MongoDB Aggregation Framework: project matches only array element

I have a class document like:

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

And I want the student who has the name "An" to receive only the corresponding element in the "students" array. I can do this with the find () function as:

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

This is normal, but when I do the same with aggregation, as shown below, it gets an error:

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

Error:

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

Why? I cannot use "$" for an array in the $ project operator aggregate (), but it can be used in the find () project operator.

+6
source share
3 answers

From docs :

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

The positional operator $ cannot be used at the design stage of the aggregation pipeline. There it is not recognized.

This makes sense because when you perform a projection along with a search, entering the projection part of the query is a single document that matches the query. The matching context is known even during projection. Thus, for each document that matches the query, a design operator is applied, and then before the next match is found.

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

When:

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

An aggregation pipeline is a set of steps. Each stage is completely unaware and independent of its previous or next stages. The set of documents goes through the stage completely before moving on to the next stage in the pipeline. The first step in this case is the $match step, all documents are filtered based on the matching condition. Entering the design phase has now installed documents that have been filtered out as part of the matching phase.

Thus, the positional operator at the design stage does not make sense, because at the current stage it does not know on what basis the fields were filtered. Therefore, $ operators are not allowed as part of field paths.

Why does it work below?

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

As you can see, the design phase receives a set of documents as input and designs the required fields. It does not depend on its previous and next stages.

+3
source

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

Your aggregation will look like

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

You can use $ filter to select a subset of the array to return based on the specified condition.

 db.class.aggregate([ { $match:{ "className: "AAA" } }, { $project: { $filter: { input: "$students", as: "stu", cond: { $eq: [ "$$stu.name", "An" ] } } } ]) 

The following example filters the Students array only for documents that have a name equal to "An".

0
source

Source: https://habr.com/ru/post/979511/


All Articles