Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB's Aggregation Framework: project only matching element of an array

I have a "class" document as:

{
className: "AAA",
students: [
   {name:"An", age:"13"},
   {name:"Hao", age:"13"},
   {name:"John", age:"14"},
   {name:"Hung", age:"12"}
   ]
}

And i want to get the student who has name is "An", get only matching element in array "students". I can do that with function find() as:

>db.class.find({"students.name":"An"}, {"students.$":true})
{
"_id" : ObjectId("548b01815a06570735b946c1"),
"students" : [ 
    {
        "name" : "An",
        "age" : "13"
    }
]}

It's fine, but when i do the same with Aggregation as following, it get error:

db.class.aggregate([
   {$match:{"students.name":'An'}},
   {$project:{"students.$":true}}
])

Error is:

uncaught exception: aggregate failed: {
    "errmsg" : "exception: FieldPath field names may not start with '$'.",
    "code" : 16410,
    "ok" : 0
}

Why? I can't use "$" for array in $project operator of aggregate() while can use this one in project operator of find().

like image 505
Deka Avatar asked Dec 12 '14 15:12

Deka


People also ask

How do I project only one element of an array in MongoDB?

Use $ in the projection document of the find() method or the findOne() method when you only need one particular array element in selected documents. See the aggregation operator $filter to return an array with only those elements that match the specified condition.

How do I get an element from an array in MongoDB?

To search the array of object in MongoDB, you can use $elemMatch operator. This operator allows us to search for more than one component from an array object.

How do you update an array element in MongoDB?

You can use the updateOne() or updateMany() methods to add, update, or remove array elements based on the specified criteria. It is recommended to use the updateMany() method to update multiple arrays in a collection.


2 Answers

From the docs:

Use $ in the projection document of the find() method or the findOne() method when you only need one particular array element in selected documents.

The positional operator $ cannot be used in an aggregation pipeline projection stage. It is not recognized there.

This makes sense, because, when you execute a projection along with a find query, the input to the projection part of the query is a single document that has matched the query.The context of the match is known even during projection. So for each document that matches the query, the projection operator is applied then and there before the next match is found.

db.class.find({"students.name":"An"}, {"students.$":true})

In case of:

db.class.aggregate([
   {$match:{"students.name":'An'}},
   {$project:{"students.$":true}}
])

The aggregation pipeline is a set of stages. Each stage is completely unaware and independent of its previous or next stages. A set of documents pass a stage completely before being passed on to the next stage in the pipeline. The first stage in this case being the $match stage, all the documents are filtered based on the match condition. The input to the projection stage is now a set of documents that have been filtered as part of the match stage.

So a positional operator in the projection stage makes no sense, since in the current stage it doesn't know on what basis the fields had been filtered. Therefore, $ operators are not allowed as part of the field paths.

Why does the below work?

db.class.aggregate([
     { $match: { "students.name": "An" },
     { $unwind: "$students" },
     { $project: { "students": 1 } }
])

As you see, the projection stage gets a set of documents as input, and projects the required fields. It is independent of its previous and next stages.

like image 50
BatScream Avatar answered Oct 06 '22 09:10

BatScream


Try using the unwind operator in the pipeline: http://docs.mongodb.org/manual/reference/operator/aggregation/unwind/#pipe._S_unwind

Your aggregation would look like

db.class.aggregate([
     { $match: { "students.name": "An" },
     { $unwind: "$students" },
     { $project: { "students": 1 } }
])
like image 39
atyagi Avatar answered Oct 06 '22 10:10

atyagi