Andersen
Andersen

Reputation: 93

MongoDB - Query an Array of Embedded Documents

I recently started using MongoDB to save data from a project, the basic idea is the following.

On the server side I receive some JSON objects on a WebSocket and store it in the database.

The data I receive looks something like this:

{ 
    {ident: "message_1"},
    data:
    [
        {id: "data_1", value : 10},
        {id: "data_2", value : 20},
        {id: "data_3", value : 40},
        {id: "data_4", value : 60},
        {id: "data_4", value : 60},
    ], 
    timestamp : 12234456
}

And I'm currently saving the entire JSON object in the database.

The server also should handle user http requests where it queries the database for the requested data. The user requests are always for one element of the data array and the timestamp. Thus the server response should look like this:

{ id: "data_1", value : 10, timestamp : 12234456}

I tried using db.collection.find with $elemMatch but my output still contains the data array and the desired element.

{
    data: 
    [{
        id: "data_1",
        value: 10
    }],
   timestamp : 12234456
}

So my question is if there's any possibility to query the database and return only the desired element of ´data´ array combined with the timestamp or I'll need re-create the response JSON object after reading the database.

Kind regards.

Upvotes: 1

Views: 717

Answers (2)

Andersen
Andersen

Reputation: 93

Although @varman answer is correct for the question, to better match my case where the array data will have a lot of elements (over 100), as @varman in the comment ´unwind´ can be expensive specially when the array contains loads of elements. So the ideal case I would prefer doing the ´unwind´ when the array only contains de desired element, to achieve that I changed the orders to the following:

  • Use "$match To get only the documents with the desired data element
{ "$match": { "data.id": "data_1" } }
  • Use $project and $filter to filter out the unwanted elements from data
   $project: {
      data: {
        $filter: {
          input: "$data",
          as: "data",
          cond: {$eq: ["$$data.id", "data_1"]}
        }
      },
      "timestamp": 1,
      "_id": 0
    }
  • Now add the timestamp field as in the correct answer
  { $addFields: {"data.timestamp": "$timestamp"} }
  • Use unwind to deconstructs data array field from the input documents to output a document for each element.
  {
    $unwind: "$data"
  }
  • Then use $replaceRoot replaces the input document with the data fields.
{"$replaceRoot": {"newRoot": "$data"}} 

Hope this will be usefull to anyone needing to do the same, and if you have any recommendation/improvements on my current setup please I'm in for advices as I'm new to MongoDB.

Running example Mongo Playground

Upvotes: 0

varman
varman

Reputation: 8894

Welcome to the mongo world. You can easily achieve this with aggregations

  • $uwnind to deconstruct the array
  • $match to get the desire value
  • $addFields to add new field to the object
  • $replaceRoot to make the object as root

Here is the code

db.collection.aggregate([
  { "$unwind": "$data" },
  { "$match": { "data.id": "data_1" } },
  { "$addFields": { "data.timestamp": "$timestamp" } },
  { "$replaceRoot": { "newRoot": "$data" } }
])

Working Mongo playground

Upvotes: 2

Related Questions