I have this aggregation query in MongoDB:
db.questions.aggregate([
{ $project:{question:1,detail:1, choices:1, answer:1,
percent_false:{
$multiply:[100,{$divide:["$answear_false",{$add:["$answear_false","$answear_true"]}]}]},
percent_true:{
$multiply:[100,{$divide:["$answear_true",{$add:["$answear_false","$answear_true"]}]}]} }}, {$match:{status:'active'} }
]).pretty()
I want using $match on 2 computed fields "percent_true" and "percent_false" like this
$match : {percent_true:{$gte:20}}
How can i do ?
Singe the aggregation framework works in stages, you can treat the computed fields as if they were normal fields because from the $match's perspective, they are normal.
{ $project:{
question:1,detail:1, choices:1, answer:1,
percent_false:{
$multiply:[100,{$divide:["$answear_false",{$add:["$answear_false","$answear_true"]}]}]
},
percent_true:{
$multiply:[100,{$divide:["$answear_true",{$add:["$answear_false","$answear_true"]}]}]}
}
},
{$match:{
status:'active',
percent_true:{$gte:20}
//When documents get fed to match they already have a percent_true field, so you can match on them as normal
}
}
Related
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
I have a collection with documents that has the following fields: Name,Date,City,Value
I created an aggregation where the group by is on the Name for getting the max(date).
From the results I need the field Value (but as you can see this field isn't in the aggregation so I don't really know how can I get it).
db.direct_client.aggregate([
{
$group : {
_id : {"name":"$name"},
count: { $sum: 1 },
max_date:{$max:"$created_date"}
}
}
])
Can someone guide me how to get this data
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".
In aggregation pipeline I have this:-
{
"$project" => {
account_name_i: { "$toLower" => "$account_name" },
}
}
{
"$sort" => {
account_name_i: 1
}
}
and I have index { account_name: 1 }
My Question is will $sort use index on account_name? If no, there is any other way we can achieve this in aggregation pipeline?
No. The aggregation pipeline can only use a standard index on a $match or $sort phase that's at the beginning of a pipeline. The rules for using indexes with aggregation pipelines are described in detail in the manual.
You aim to sort on the lower-case version of account_name_i, most likely to function as a case-insensitive sort. To achieve this with an index, store a lower-case-normalized version of account_name_i in each document
{ "account_name_i" : "TruMAn's HABerdaShEry", "account_name_i_lc" : "truman's haberdashery" }
and index the normalized field ({ "account_name_i_lc" : 1 }).
If you are using Aggregation Pipeline Stages like $group or $match on a collection then it will
produce a new set of documents from that collection.
as it is new set of documents which means no index is defined for it and hence we cant use the index in Aggregation Pipeline
Note: although if you use $sort as first stage of Aggregation Pipeline then it will utilize the index of
the collection as input for this stage is collection itself.
Kind of, though only with preprocessing. You can do an output after your project stage and query the output collection for sorting:
db.yourSource.aggregate([
{
"$project" => {
account_name_i: { "$toLower" => "$account_name" },
}
},
{
$out: "intermediate"
}
])
db.intermediate.ensureIndex({account_name_i:1})
db.intermediate.find({}).sort({account_name_i:1})
I would like to define a $project aggregation stage where I can instruct it to add a new field and include all existing fields, without having to list all the existing fields.
My document looks like this, with many fields:
{
obj: {
obj_field1: "hi",
obj_field2: "hi2"
},
field1: "a",
field2: "b",
...
field26: "z"
}
I want to make an aggregation operation like this:
[
{
$project: {
custom_field: "$obj.obj_field1",
//the next part is that I don't want to do
field1: 1,
field2: 1,
...
field26: 1
}
},
... //group, match, and whatever...
]
Is there something like an "include all fields" keyword that I can use in this case, or some other way to avoid having to list every field separately?
In 4.2+, you can use the $set aggregation pipeline operator which is nothing other than an alias to $addFieldsadded in 3.4
The $addFields stage is equivalent to a $project stage that explicitly specifies all existing fields in the input documents and adds the new fields.
db.collection.aggregate([
{ "$addFields": { "custom_field": "$obj.obj_field1" } }
])
You can use $$ROOT to references the root document. Keep all fields of this document in a field and try to get it after that (depending on your client system: Java, C++, ...)
[
{
$project: {
custom_field: "$obj.obj_field1",
document: "$$ROOT"
}
},
... //group, match, and whatever...
]
>>> There's something like "include all fields" keyword that I can use in this case or some another solution?
Unfortunaly, there is no operator to "include all fields" in aggregation operation. The only reason, why, because aggregation is mostly created to group/calculate data from collection fields (sum, avg, etc.) and return all the collection's fields is not direct purpose.
To add new fields to your document you can use $addFields
from docs
and to all the fields in your document, you can use $$ROOT
db.collection.aggregate([
{ "$addFields": { "custom_field": "$obj.obj_field1" } },
{ "$group": {
_id : "$field1",
data: { $push : "$$ROOT" }
}}
])
As of version 2.6.4, Mongo DB does not have such a feature for the $project aggregation pipeline. From the docs for $project:
Passes along the documents with only the specified fields to the next stage in the pipeline. The specified fields can be existing fields from the input documents or newly computed fields.
and
The _id field is, by default, included in the output documents. To include the other fields from the input documents in the output documents, you must explicitly specify the inclusion in $project.
according to #Deka reply, for c# mongodb driver 2.5 you can get the grouped document with all keys like below;
var group = new BsonDocument
{
{ "_id", "$groupField" },
{ "_document", new BsonDocument { { "$first", "$$ROOT" } } }
};
ProjectionDefinition<BsonDocument> projection = new BsonDocument{{ "document", "$_document"}};
var result = await col.Aggregate().Group(group).Project(projection).ToListAsync();
// For demo first record
var fistItemAsT = BsonSerializer.Deserialize<T>(result.ToArray()[0]["document"].AsBsonDocument);