dubRun
dubRun

Reputation: 245

Mongodb how can I query a subset of an array for a specific document

I want to run a query to select specific documents. Then on each document, open up an array of sub documents and run a query to filter those sub docs.

Example:

{
  "_id" : ObjectID(23412351346435),
  "list" : [
    {date: ISODate(2015-01-12T00:00:00.000Z), name: "Jan 12"},
    {date: ISODate(2015-01-13T00:00:00.000Z), name: "Jan 13"},
    {date: ISODate(2015-01-14T00:00:00.000Z), name: "Jan 14"}
  ]
}

I'm guessing I can do something with Mongo's aggregate function. I have been able to match the documents I want, but how can I get a sub query going on the array? I tried using $elemMatch but that only returns the first item in the array that matches the date range.

To be clear when I query for ObjectID(23412351346435) and Date range 2015-01-12 to 2015-01-13 I want it to return this;

{
   "_id" : ObjectID(23412351346435),
   "list" : [
        {date: ISODate(2015-01-12T00:00:00.000Z), name: "Jan 12"},
        {date: ISODate(2015-01-13T00:00:00.000Z), name: "Jan 13"}
   ]
}

Upvotes: 1

Views: 1661

Answers (2)

Juan Carlos Farah
Juan Carlos Farah

Reputation: 3879

As you guessed, you can use aggregation to get the results you are looking for. The steps you need in your aggregation pipeline are the following.

  1. Match the documents you want. (I've done that here by _id).
  2. Unwind the list array.
  3. Match the dates you are looking for using a range query.
  4. Group the documents back by _id.

Your query should look something like this:

db.collection.aggregate([   
    { "$match": { "_id": ObjectId("54ef8b0acfb269d664de0b48")} },
    { "$unwind": "$list" },
    { "$match": {
        "list.date": { $gte: ISODate("2015-01-12T00:00:00.000Z"),
                       $lte: ISODate("2015-01-13T00:00:00.000Z")
        }
    }},
    { "$group": {
        "_id": "$_id",
        "list": { "$push": { "date": "$list.date", "name": "$list.name" }}
    }}
]);

Upvotes: 2

Spencer Rathbun
Spencer Rathbun

Reputation: 14900

You can use the $unwind operator. It allows you to take an array in a document, and clone that document with each of the array elements. Then you can match on the field. If necessary, you can use $group to wind the document back up.

[
    {$match:{...}},
    {$unwind:"myfield"},
    {$match:{"myfield.name":"Jan 12"}},
    {$group:{ _id:"$id", "myfield":{$push:"$myfield"} }}
]

Note that $unwind is slow for large sets of documents, and combinatoric for multiple arrays. It is the only option you really have for Mongo 2.4.x though. You might be better served by rearranging your data so that you do not have arrays.

Upvotes: 1

Related Questions