when will the updated values set in the $set stage be reflected in the pipeline? - mongodb

db.col.aggregate( [ $set: { A: 4, B: "$A" } ] )
Will the field B also be set to 4 within the same stage?

tested.
It will not.
Fields values that are set during the $set stage will only reflect in the stages after.

Related

Use MongoDB $sort twice

Is there a way to use the $sort operator twice within a single aggregation pipeline?
I know that using a singular $sort with two keys works properly, i.e. sort by the first key, then the second.
My current project requires multiple $sort stages to exist, for example
db.collection.aggregate([
{
$sort: {
"age": 1
}
},
{
$sort: {
"score": -1
}
}
])
Currently, the second stage doesn't respect the result of the first stage. Is there any workaround for that?
Is it possible to, for example, assign each document a new field 'index' after the first stage, storing its index within the current array of results, and use that field in the second $sort stage?
You can use multiple value in '$sort'.
db.collection.aggregate([
{
"$sort": {
"age": 1,
"score": -1
}
}
])
I have define mongo playground link, you can refer it
https://mongoplayground.net/p/ZaRX_XNSXhu

Mongodb aggregation pipeline stages

I have an aggregation of say 4 stages match,lookup, unwind, project as mentioned in the same order.
Suppose if the resultant data from the match stage is null that is it does not return any document then does the aggregation breaks? or it passes null data to next stage and executes all the next stages?
If it executes all the next stage then how to break the aggregation when the result of match query is null.
I am asking this so that i can minimise the query to the database.
To answer your question, if it is null, it will execute the stages and returns empty result. You don't need to break it if it is null.
Play
The MongoDB aggregation pipeline consists of stages. Each stage transforms the documents as they pass through the pipeline.
Reference
Explanation:
db.orders.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } }
])
First Stage: The $match stage filters the documents by the status field and passes to the next stage those documents that have status equal to "A".
Second Stage: The $group stage groups the documents by the cust_id field to calculate the sum of the amount for each unique cust_id.

Sort a mongo collection by a string field [duplicate]

I want to sort a collection by putting items with a specific values before other items.
For example I want all the items with "getthisfirst": "yes" to be before all the others.
{"getthisfirst": "yes"}
{"getthisfirst": "yes"}
{"getthisfirst": "no"}
{"getthisfirst": "maybe"}
This as a general concept is called "weighting". So without any other mechanism in place, then you handle this logically in a MongoDB query by "projecting" the values for the "weight" into the document logically.
Your method for "projecting" and altering the fields present in your document is the .aggregate() method, and specifically it's $project pipeline stage:
db.collection.aggregate([
{ "$project": {
"getthisfirst": 1,
"weight": {
"$cond": [
{ "$eq": [ "$getthisfirst", "yes" ] },
10,
{ "$cond": [
{ "$eq": [ "$getthisfirst", "maybe" ] },
5,
0
]}
]
}
}},
{ "$sort": { "weight": -1 } }
]);
The $cond operator here is a "ternary" ( if/then/else ) condition where the first argument is a conditional statment arriving to boolean true|false. If true "then" the second argument is returned as the result, otherwise the "else" or third argument is returned in response.
In this "nested" case, then where the "yes" is a match then a certain "weight" score is assigned, otherwise we move on to the next condition test where when "maybe" is a match then anoter score is assigned, or otherwise the score is 0 since we only have three posibilities to match.
Then the $sort condition is applied in order to, well "order" ( in decending order ) the results with the largest "weight" on top.

Conditionally include a field (_id or other) in mongodb project aggregation?

I've got a mongodb aggregation pipeline with a $project stage and I'd like to include certain fields only if conditions are met. Specifically, I'd like to exclude the _id in one condition and include a second field 'second_id' in the other condition.
I know that it's not possible (yet) to exclude fields from a mongodb $project, but is it possible to conditionally include them?
Is there a way to conditionally exclude the _id field? It accepts a 0 or 1, but what if I want to determine that 0 or 1 based on an if statement. How would this be done?
Pseudocode for _id:
$project: { _id: { $ifNull: [ "$user_id", 0 ] } }
The main use of this would be to use the doc.user_id as the result _id, or allow mongodb to create a new autoincrement _id if that user_id is null.
There isn't a way currently to do this within the $project stage, but you can use the $redact stage to remove the field conditionally (i.e. you set the value to 0 like you are doing in your example.
db.collection.aggregate(
... matching and stuff ...
{$project: { _id: { $ifNull: [ "$user_id", 0 ] } }},
{$redact: {
{$cond: {
if: { $eq: [ "$user_id", 0 ] },
then: '$$PRUNE',
else: '$$DESCEND'
}}
}
I have a trick for this one:
"_id": {"$cond": [{"$eq": ["$user_id", null]}, "$nonExistinField", "$user_id"]}
Maybe this was not doable when the question was asked, but now it is.
There is a way to conditionally include/exclude a particular field in $project stage.
This can be achieved using $cond to evaluate your condition, and then return either '$true' or '$false'
{
myConditionalField:{$cond:{
if:{
$eq:['$some_field','some_value']
},
then:'$false',
else:'$true'
}}
}
Here, the field myConditionalField will be projected only if the value of some_field from previous stage matched some_value.
NOTE: Its assumed here that myConditionalField already exists from a previous stage. If it doesn't, then the else should be $some_field or whatever field you want to be projected.

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().
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.
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 } }
])
You can use $filter to selects a subset of an 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 to only include documents that have a name equal to "An".