how i can find the document with $match on position 3 (only last item in array "ndr"). It is necessary that the aggreation search only in the last array-item of ndr.
{
"_id" : ObjectId("58bd5c63a3d24b4a2e4cde03"),
"name" : "great document",
"country" : "us_us",
"cdate" : ISODate("2017-03-06T12:56:03.405Z"),
"nodes" : [
{
"node" : 3244343,
"name" : "Best Node ever",
"ndr" : [
{
"position" : 7,
"cdate" : ISODate("2017-03-06T10:55:20.000Z")
},
{
"position" : 3,
"cdate" : ISODate("2017-03-06T10:55:20.000Z")
}
]
}
],
}
I need this result after aggregation
{
"name" : "great document",
"country" : "us_us",
"cdate" : ISODate("2017-03-06T12:56:03.405Z"),
"nodes" : [
{
"node" : 3244343,
"name" : "Best Node ever",
"ndr" : [
{
"position" : 3,
"cdate" : ISODate("2017-03-06T10:55:20.000Z")
}
]
}
]
}
I hope anyone can help me.
You can try below aggregation with Mongo 3.4 version.
The below query finds the last item (-1) using $arrayElemAt operator in the ndr array and stores the variable in last using $let operator for each nodes and compare the last variable position value using $$ notation to 3 and wraps the nbr element within array [] if entry found and else returns empty array.
$map operator to reach nbr array inside the nodes array and project the updated nbr array while mapping the rest of nodes fields.
$addFields stage will overwrite the existing nodes with new nodes while keeping the all the other fields.
db.collection.aggregate([{
$addFields: {
nodes: {
$map: {
input: "$nodes",
as: "value",
in: {
node: "$$value.node",
name: "$$value.name",
ndr: {
$let: {
vars: {
last: {
$arrayElemAt: ["$$value.ndr", -1]
}
},
in: {
$cond: [{
$eq: ["$$last.position", 3]
},
["$$last"],
[]
]
}
}
}
}
}
}
}
}]);
Update:
You can try $redact which will keep the whole document if it finds the matching position from with the given filter.
$map to project the true, false values based on the filter for each of the nodes nbr position value and $anyElementTrue will inspect the previous boolean values for each doc and return a true or false value and $redact will use the booelan value from above comparison; true value to keep and false value to remove the document.
db.collection.aggregate([{
$redact: {
$cond: [{
$anyElementTrue: {
$map: {
input: "$nodes",
as: "value",
in: {
$let: {
vars: {
last: {
$arrayElemAt: ["$$value.ndr", -1]
}
},
in: {
$cond: [{
$eq: ["$$last.position", 3]
},
true,
false
]
}
}
}
}
}
}, "$$KEEP", "$$PRUNE"]
}
}]);
you will need to unwind both nested arrays.
db.<collection>.aggregate([
{ $unwind: '$nodes' },
{ $unwind: '$nodes.ndr'},
{ $group: {'_id':{'_id':'$_id', 'nodeID', '$nodes.node' },
'name':{'$last':'$name'},
'country':{'$last':'$country'},
'cdate':{'$last':'$cdate'},
'nodes':{'$last':'$nodes'}
}
},
{ $match : { nodes.ndr.position: 3 } }
]);
From here you can reassemble the aggregate results with a $group on the and do a projection. I'm not sure what your ultimate end result should be.
Related
I'm using MongoDB's aggregation.
Using $in I'm checking if a value exists in an array or not, And I want to return the matched object from calificacion array into calificacion_usuario field. (my _id is unique in the array).
I tried using $first" to return the current element. It's not working, not sure why & what to use.
How can I do it?
Sample Doc :
{
"cargo" : "Presidente",
"calificacion" : [
{
"_id" : "5e894ae6fa9fd23780bcf472",
"estrellas" : 3
},
{
"_id" : "5e895187fa9fd23780bcf478",
"estrellas" : 5
}
]}
Query :
Politico.aggregate([
{
$group: {
_id: "$cargo",
nuevo_formato: {
$push: {
$mergeObjects: [
"$$ROOT",
{
"calificacion_promedio": { $avg: "$calificacion.estrellas" },
"calificacion_usuario": { $cond: [{ $in: [req.body.id_usuario, "$calificacion._id"] }, "$first", false] },
"calificacion_numero_encuestas": { $size: "$calificacion" }
}
]
}
}
}
},
$first doesn't work that way, You need to replace $first with below code :
{
$arrayElemAt: [ /** Get an element from 'calificacion' based on position */
"$calificacion",
{
$indexOfArray: ["$calificacion._id", req.body.id_usuario], /** Check in which position 'req.body.id_usuario' exists (will be a number) */
},
],
}
Test : MongoDB-Playground
Ref : $indexOfArray, $arrayElemAt
New to Mongo, have found lots of examples of removing dupes from arrays of strings using the aggregation framework, but am wondering if possible to remove dupes from array of objects based on a field in the object. Eg
{
"_id" : ObjectId("5e82661d164941779c2380ca"),
"name" : "something",
"values" : [
{
"id" : 1,
"val" : "x"
},
{
"id" : 1,
"val" : "x"
},
{
"id" : 2,
"val" : "y"
},
{
"id" : 1,
"val" : "xxxxxx"
}
]
}
Here I'd like to remove dupes based on the id field. So would end up with
{
"_id" : ObjectId("5e82661d164941779c2380ca"),
"name" : "something",
"values" : [
{
"id" : 1,
"val" : "x"
},
{
"id" : 2,
"val" : "y"
}
]
}
Picking the first/any object with given id works. Just want to end up with one per id. Is this doable in aggregation framework? Or even outside aggregation framework, just looking for a clean way to do this. Need to do this type of thing across many documents in collection, which seems like a good use case for aggregation framework, but as I mentioned, newbie here...thanks.
Well, you may get desired result 2 ways.
Classic
Flatten - Remove duplicates (pick first occurrence) - Group by
db.collection.aggregate([
{
$unwind: "$values"
},
{
$group: {
_id: "$values.id",
values: {
$first: "$values"
},
id: {
$first: "$_id"
},
name: {
$first: "$name"
}
}
},
{
$group: {
_id: "$id",
name: {
$first: "$name"
},
values: {
$push: "$values"
}
}
}
])
MongoPlayground
Modern
We need to use $reduce operator.
Pseudocode:
values : {
var tmp = [];
for (var value in values) {
if !(value.id in tmp)
tmp.push(value);
}
return tmp;
}
db.collection.aggregate([
{
$addFields: {
values: {
$reduce: {
input: "$values",
initialValue: [],
in: {
$concatArrays: [
"$$value",
{
$cond: [
{
$in: [
"$$this.id",
"$$value.id"
]
},
[],
[
"$$this"
]
]
}
]
}
}
}
}
}
])
MongoPlayground
You can use $reduce, Try below query :
db.collection.aggregate([
{
$addFields: {
values: {
$reduce: {
input: "$values",
initialValue: [],
in: {
$cond: [
{ $in: ["$$this.id", "$$value.id"] }, /** Check if 'id' exists in holding array if yes push same array or concat holding array with & array of new object */
"$$value",
{ $concatArrays: ["$$value", ["$$this"]] }
]
}
}
}
}
}
]);
Test : MongoDB-Playground
My document structure looks like this:
{
"_id" : ObjectId("5aeeda07f3a664c55e830a08"),
"profileId" : ObjectId("5ad84c8c0e71892058b6a543"),
"list" : [
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
],
}
I want to count array of
list field. And apply condition before slicing that
if the list<=10 then slice all the elements of list
else 10 elements.
P.S I used this query but is returning null.
db.getCollection('post').aggregate([
{
$match:{
profileId:ObjectId("5ada84c8c0e718s9258b6a543")}
},
{$project:{notifs:{$size:"$list"}}},
{$project:{notifications:
{$cond:[
{$gte:["$notifs",10]},
{$slice:["$list",10]},
{$slice:["$list","$notifs"]}
]}
}}
])
Your first $project stage effectively wipes out all result fields but the one(s) that it explicitly projects (only notifs in your case). That's why the second $project stage cannot $slice the list field anymore (it has been removed by the first $project stage).
Also, I think your $cond/$slice combination can be more elegantly expressed using the $min operator. So there's at least the following two fixes for your problem:
Using $addFields:
db.getCollection('post').aggregate([
{ $match: { profileId: ObjectId("5ad84c8c0e71892058b6a543") } },
{ $addFields: { notifs: { $size: "$list" } } },
{ $project: {
notifications: {
$slice: [ "$list", { $min: [ "$notifs", 10 ] } ]
}
}}
])
Using a calculation inside the $project - this avoids a stage so should be preferable.
db.getCollection('post').aggregate([
{ $match: { profileId: ObjectId("5ad84c8c0e71892058b6a543") } },
{ $project: {
notifications: {
$slice: [ "$list", { $min: [ { $size: "$list" }, 10 ] } ]
}
}}
])
I have this lab test in the mongodb course i am currently taking, the movies collection have a title field and the instruction says:
Using only $project aggregation.
find the movie titles composed of only 1 word like "Cinderella" and "3-25" should count where as "Cast Away" would not.
Use $split String expression and $size Array expression.
Here's a sample document from movies collection:
{
"_id" : ObjectId("573a1390f29313caabcd4192"),
"title" : "The Conjuring of a Woman at the House of Robert Houdin",
"year" : 1896,
"runtime" : 1,
"cast" : [
"Jeanne d'Alcy",
"Georges M�li�s"
]
}
And here's my code:
var pipeline = [
{ $project: {
"title": { $split: ["$title"," "] }
} },
{ $project: {
"_id": 0,
"title_size": {$eq: [{$size: "$title"}, 1]},
"Movie": "$title"
} }
]
db.movies.aggregate(pipeline)
The $eq returns boolean values true and false, not what i expected, then i tried the $literal: 1as the second expression of $eq but i get the same boolean values
What i wanted to achieved is this:
{ "title_size" : 1, "Movie" : [ "Cinderella" ] }
But how?
[
{
$project: {
splitedTitles: {$split: ["$title", " "]}
}
},
{
$match : { splitedTitles : { $size: 1 } }
}
]
I recently found difficulty in finding an object stored in a document with its key in another field of that same document.
{
list : {
"red" : 397n8,
"blue" : j3847,
"pink" : 8nc48,
"green" : 983c4,
},
result : [
{ "id" : 397n8, value : "anger" },
{ "id" : j3847, value : "water" },
{ "id" : 8nc48, value : "girl" },
{ "id" : 983c4, value : "evil" }
]
}
}
I am trying to get the value for 'blue' which has an id of 'j3847' and a value of 'water'.
db.docs.find( { result.id : list.blue }, { result.value : 1 } );
# list.blue would return water
# list.pink would return girl
# list.green would return evil
I tried many things and even found a great article on how to update a value using a value in the same document.: Update MongoDB field using value of another field which I based myself on; with no success... :/
How can I find a MongoDB object using value of another field ?
You can do it with the $filter operator within mongo aggregation. It returns an array with only those elements that match the condition:
db.docs.aggregate([
{
$project: {
result: {
$filter: {
input: "$result",
as:"item",
cond: { $eq: ["$list.blue", "$$item.id"]}
}
}
}
}
])
Output for this query looks like this:
{
"_id" : ObjectId("569415c8299692ceedf86573"),
"result" : [ { "id" : "j3847", "value" : "water" } ]
}
One way is using the $where operator though would not recommend as using it invokes a full collection scan regardless of what other conditions could possibly use an index selection and also invokes the JavaScript interpreter over each result document, which is going to be considerably slower than native code.
That being said, use the alternative .aggregate() method for this type of comparison instead which is definitely the better option:
db.docs.aggregate([
{ "$unwind": "$result" },
{
"$project": {
"result": 1,
"same": { "$eq": [ "$list.blue", "$result.id" ] }
}
},
{ "$match": { "same": true } },
{
"$project": {
"_id": 0,
"value": "$result.value"
}
}
])
When the $unwind operator is applied on the result array field, it will generate a new record for each and every element of the result field on which unwind is applied. It basically flattens the data and then in the subsequent $project step inspect each member of the array to compare if the two fields are the same.
Sample Output
{
"result" : [
{
"value" : "water"
}
],
"ok" : 1
}
Another alternative is to use the $map and $setDifference operators in a single $project step where you can avoid the use of $unwind which can be costly on very large collections and in most cases result in the 16MB BSON limit constraint:
db.docs.aggregate([
{
"$project": {
"result": {
"$setDifference": [
{
"$map": {
"input": "$result",
"as": "r",
"in": {
"$cond": [
{ "$eq": [ "$$r.id", "$list.blue" ] },
"$$r",
false
]
}
}
},
[false]
]
}
}
}
])
Sample Output
{
"result" : [
{
"_id" : ObjectId("569412e5a51a6656962af1c7"),
"result" : [
{
"id" : "j3847",
"value" : "water"
}
]
}
],
"ok" : 1
}