Mongodb 3.2 and 3.0 $unwind aggregation - mongodb

I have created a query and check it in robomongo and it's working fine for me in mongodb 3.2
db.post.aggregate([
{$unwind: {path: "$page_groups", preserveNullAndEmptyArrays: true}},
{$group: {_id: "$page_groups",
page_names: {$addToSet: "$page_name"}}},
])
But unfortunantly I need to get same data in mongodb 3.0
Can anyone tell me how to get data with empty array in mongo 3.0 and get results by array key?
Without $unwind I get objects where pages have two or more groups and I don't need it.

Thank you for answere, I wanted to use $project at first, but I think I have found easier way using $match and array $size to ignore results where array gets more than one element:
db.post_summary.aggregate([
{$match: {$or:
[{page_groups: {$size: 1}}, {page_groups: {$size: 0}}]}},
{$group: {
_id: "$page_groups",
page_names: { "$addToSet": "$page_name" }
}},
])
In my case "page_groups" have this structure:
page_groups:[
0 =>[_id, group_name]
1 =>[_id, group_name]
]

To mimick the preserveNullAndEmptyArrays $unwind option in 3.2 for 3.0 aggregation pipeline operations, generate an initial $project pipeline stage that creates the array field if it's null or empty (using the $ifNull operator):
var pipeline = [
{
"$project": {
"pg": {
"$ifNull": [
"$page_groups",
["Unspecified"]
]
},
"page_name": 1
}
},
{ "$unwind": "$page_groups" },
{
"$group": {
"_id": "$page_groups",
"page_names": { "$addToSet": "$page_name" }
}
}
];
db.collection.aggregate(pipeline);

Related

Project nested array element to top level using MongoDB aggregation pipeline

I have a groups collection with documents of the form
{
"_id": "g123"
...,
"invites": [
{
"senderAccountId": "a456",
"recipientAccountId": "a789"
},
...
]
}
I want to be able to list all the invites received by a user.
I thought of using an aggregation pipeline on the groups collection that filters all the groups to return only those to which the user has been invited to.
db.groups.aggregate([
{
$match: {
"invites.recipientAccountID": "<user-id>"
}
}
])
Lastly I want to project this array of groups to end up with an array of the form
[
{
"senderAccountId": "a...",
"recipientAccountId": "<user-id>",
"groupId": "g...", // Equal to "_id" field of document.
},
...
]
But I'm missing the "project" step in my aggregation pipeline to bring to the top-level the nested senderAccountId and recipientAccountId fields. I have seen examples online of projections in MongoDB queries and aggregation pipelines but I couldn't find examples for projecting the previously matched element of an array field of a document to the top-level.
I've thought of using Array Update Operators to reference the matched element but couldn't get any meaningful progress using this method.
There are multiple ways to do this, using a combination of unwind and project would work as well. Unwind will create one object for each and project let you choose how you want to structure your result with current variables.
db.collection.aggregate([
{
"$unwind": "$invites"
},
{
"$match": {
"invites.recipientAccountId": "a789"
}
},
{
"$project": {
recipientAccountId: "$invites.recipientAccountId",
senderAccountId: "$invites.senderAccountId",
groupId: "$_id",
_id: 0 // don't show _id key:value
}
}
])
You can also use nimrod serok's $replaceRoot in place of the $project one
{$replaceRoot: {newRoot: {$mergeObjects: ["$invites", {group: "$_id"}]}}}
playground
nimrod serok's solution might be a bit better because mine unwind it first and then matches it but I believe mine is more readable
I think what you want is $replaceRoot:
db.collection.aggregate([
{$match: {"invites.recipientAccountId": "a789"}},
{$set: {
invites: {$first: {
$filter: {
input: "$invites",
cond: {$eq: ["$$this.recipientAccountId", "a789"]}
}
}}
}},
{$replaceRoot: {newRoot: {$mergeObjects: ["$invites", {group: "$_id"}]}}}
])
See how it works on the playground example

Removing item out of nested document array and while also accounting for null/empty document array

I'm new to mongodb and I've been working on this query for quite sometime. I've found solutions using "$project" and "$group" and "$match". Overall goal is if document within nested array "internal" attribute is false, remove it from the array.
$project and $group DO work BUT they then throw of the projection, I don't even see a current projection in this query but once I add in $project or $group it ONLY returns the specific nested document array I'm messing with.
$match won't work because I have cases where the parameter in question that I'm using to remove items from the nested document array is true or false or the array is empty, and $match in different use cases just doesn't return the main document.
Here's an example $group
{ '$unwind': '$notes' },
{
$group: {
_id: "$_id",
notes: {
$push: {
$cond: {
if: { $eq: [ "$notes.internal", false ] },
then: "$$REMOVE",
else: "$notes.internal"
}
}
}
}
You may be able to use $addFields with $filter:
{$addFields: {
notes: {$filter: {
input: "$notes",
as: "item",
cond: {$ne: [ "$$item.internal", false ]}
}}
}}

Indexing an aggregation slow query on mongo db

I got a slow query on mongo about around 50k documents in a collection
How can I index it?
I tried to add the following index but it does not solve the issue
db.getCollection("events").createIndex({ "area.area_id": 1, "execute_time": -1 })
"Slow query","attr":{"type":"command","ns":"events.events",
"command":{"aggregate":"events","pipeline":
[
{"$facet":{"1":[{"$match":{"area.area_id":"1"}},
{"$sort":{"execute_time":-1}},{"$limit":30}
],
"2":
[
{"$match":{"area.area_id":"2"}},
{"$sort":{"execute_time":-1}},{"$limit":30}]}}
]
,"cursor":{},
"lsid":
{"id":{"$uuid":"2be3c461-dfc7-4591-adaf-da9454b9615c"}},"$db":"events"},
"planSummary":"COLLSCAN","keysExamined":0,"docsExamined":37973,"cursorExhausted":true,"numYields":37,"nreturned":1,
"reslen":118011,"locks":{"ReplicationStateTransition":{"acquireCount":{"w":61}},"Global":{"acquireCount":{"r":61}},
"Database":{"acquireCount":{"r":61}},"Collection":{"acquireCount":{"r":61}},"Mutex":{"acquireCount":{"r":24}}},"storage":{},"protocol":"op_msg","durationMillis":262}}
my query:
this.collection.aggregate([
{$facet: facetObj }])
each facet obj is something like:
facet[x] = [
{$match: {'area.area_id': x}},
{$sort: { execution_time: -1 }},
{$limit: limit}
]
You cannot use indexes in the $facet stage.
From the MongoDB documentation:
The $facet stage, and its sub-pipelines, cannot make use of indexes, even if its sub-pipelines use $match or if $facet is the first stage in the pipeline. The $facet stage will always perform a COLLSCAN during execution.
You did not show any input data nor the expected result. However from what I see, one approach could be this one:
db.collection.aggregate([
{ $match: { area_id: { $in: [ 1, 2 ] } } },
{ $sort: { execute_time: -1 } },
{
$group: {
_id: "$area_id",
execute_time: { $push: "$execute_time" }
}
},
{
$set: {
execute_time: { $slice: [ "$execute_time", 30 ] }
}
}
])
Mongo playground

Mongodb $match using field added with $addFields

How can I use a field that I just added in the $addFields stage to the following $match stage?
This will return no result:
db.getCollection('myCollection').aggregate([
{$addFields: { "test": ISODate("2018-02-15T03:22:21.000Z")}},
{$match: { $or: [{"timestamp":"$test"}]}}
])
This one will return expected result:
db.getCollection('myCollection').aggregate([
{$addFields: { "test": ISODate("2018-02-15T03:22:21.000Z")}},
{$match: { $or: [{"timestamp":ISODate("2018-02-15T03:22:21.000Z")}]}}
])
How comes that the $test is not resolved in the $match stage?
EDIT
I finally post myself a solution for mongdb 2.4 thanks to this answer. Solutions are similar but the problem are not expressed the same way
I think Veeram answer is valid for mongodb version 3.6 but I'm running with version 3.4
I finally found this way to be able to use a field added in a $addFields stage:
db.getCollection('myCollection').aggregate([
{$addFields: { "myDate": ISODate("2018-02-15T03:22:21.000Z")}},
{$project:{test: {$cond:[{$eq:["$timestamp", "$myDate"]},1,0]}}},
{$match: {"test": {"$eq": 1}}}
])
or with the $redact stage:
db.getCollection('myCollection').aggregate([
{$addFields: { "test": ISODate("2018-02-15T03:22:21.000Z")}},
{$redact: {$cond: [{ $eq: [ "$timestamp", "$test" ] }, "$$KEEP", "$$PRUNE"]}}
])
I do not know why it is working with (addFields + project + match) and with (addFields + redact) but not with (addFields + match) but I will go with this solution as migration is not yet foreseen

$match operator for sub document field in MongoDb

I am trying new pipeline query of MongoDB so i try to execute below query.
{
aggregate: 'Posts',
pipeline: [
{ $unwind: '$Comments'},
{ $match: {'$Comments.Owner': 'Harry' }},
{$group: {
'_id': '$Comments._id'
}
}
]
}
And nothing match to query so empty result returns. I guess problem can be on $match command . I am using dotted notation match comment Owner but not sure it is exactly true or not. Why this query does not return Ownders who is 'Harry' . I am sure it is exist in db.
You don't use the $ prefix for the $match field names.
Try this:
{
aggregate: 'Posts',
pipeline: [
{ $unwind: '$Comments'},
{ $match: {'Comments.Owner': 'Harry' }},
{ $group: {
'_id': '$Comments._id'
}}
]
}
I encounter the same problem with aggregation framework with MongoDB 2.2.
$match didn't work for me for subdocument (but I am just learning MongoDB, so I could do something wrong).
I added extra projection to remove subdocument (Comments in this case):
{
aggregate: 'Posts',
pipeline: [
{ $unwind: '$Comments'},
{ $project: {
comment_id: "$Comments._id",
comment_owner: "$Comments.Owner"
}},
{ $match: {'$comment_Owner': 'Harry' }},
{$group: {
'_id': '$comment_id'
}
}
]
}