MongoDB Combine Unwinded Documents - mongodb

I am creating a mongo aggregate pipeline where I unwind a document by a field (doors) that is a list. Then, I filter by a condition on a property in the individual door.
How would I combine the filtered results back together such that the result is in its original form?
Here is an example of a document in my collection:
{ "uuid": "00000000-0000-0000-0000-000000000000",
"name": "Building1",
"doors": [
{
"doorUuid": "11111111-1111-1111-1111-111111111111",
"creationTime": null
},
{
"doorUuid": "22222222-2222-2222-2222-222222222222",
"creationTime": 1560194908942
},
{
"doorUuid": "33333333-3333-3333-3333-333333333333",
"creationTime": 1560195008942
}
]
}
For example, if I wanted to filter out all doors with null creationTime's, the output I want would have the same structure above but with only two doors.

$group with $push:
MongoPlayground
db.collection.aggregate([
{
$unwind: "$doors"
},
{
$match: {
"doors.creationTime": {
$ne: null
}
}
},
{
$group: {
_id: "$_id",
name: {
$last: "$name"
},
uuid: {
$last: "$uuid"
},
doors: {
$push: "$doors"
}
}
}
])

Related

MongoDB: How to merge all documents into a single document in an aggregation pipeline

I have the current aggregation output as follows:
[
{
"courseCount": 14
},
{
"registeredStudentsCount": 1
}
]
The array has two documents. I would like to combine all the documents into a single document having all the fields in mongoDB
db.collection.aggregate([
{
$group: {
_id: 0,
merged: {
$push: "$$ROOT"
}
}
},
{
$replaceRoot: {
newRoot: {
"$mergeObjects": "$merged"
}
}
}
])
Explained:
Group the output documents in one field with push
Replace the document root with the merged objects
Plyaground
{
$group: {
"_id": "null",
data: {
$push: "$$ROOT"
}
}
}
When you add this as the last pipeline, it will put all the docs under data, but here data would be an array of objects.
In your case it would be
{ "data":[
{
"courseCount": 14
},
{
"registeredStudentsCount": 1
}
] }
Another approach would be,
db.collection.aggregate([
{
$group: {
"_id": "null",
f: {
$first: "$$ROOT",
},
l: {
$last: "$$ROOT"
}
}
},
{
"$project": {
"output": {
"courseCount": "$f.courseCount",
"registeredStudentsCount": "$l.registeredStudentsCount"
},
"_id": 0
}
}
])
It's not dynamic as first one. As you have two docs, you can use this approach. It outputs
[
{
"output": {
"courseCount": 14,
"registeredStudentsCount": 1
}
}
]
With extra pipeline in the second approach
{
"$replaceRoot": {
"newRoot": "$output"
}
}
You will get the output as
[
{
"courseCount": 14,
"registeredStudentsCount": 1
}
]

How to remove duplicate objects with different parameters in an aggregation in mongo db

[
{ id:1,month:5,year:2020,text:"Completed" },
{ id:2,month:2,year:2021,text:"Pending" },
{ id:3,month:3,year:2020,text:"Completed" },
{ id:4,month:5,year:2020,text:"Pending" },
{ id:5,month:4,year:2022,text:"Pending" },
]
These are the documents in my collection. I need to remove remove the duplicate objects with same year & month using aggregation in mongo db. so that i get
[
{ id:1,month:5,year:2020,text:"Completed" },
{ id:2,month:2,year:2021,text:"Pending" },
{ id:3,month:3,year:2020,text:"Completed" },
{ id:5,month:4,year:2022,text:"Pending" },
]
Maybe something like this:
db.collection.aggregate([
{
$group: {
_id: {
month: "$month",
year: "$year"
},
cnt: {
$sum: 1
},
doc: {
$push: "$$ROOT"
}
}
},
{
$match: {
cnt: {
$gt: 1
}
}
},
{
$project: {
docsTodelete: {
$slice: [
"$doc",
1,
{
"$size": "$doc"
}
]
}
}
},
{
$unwind: "$docsTodelete"
}
]).forEach(function(doc){
db.backup.save(doc.docsTodelete);
db.collection.remove(_id:doc.docsToDelete._id)
})
explained:
Group the documents by month-year and push the originals to array doc
Match only the documents that have duplicates
Slice the documents array to leave 1x document in the collection
Unwind the array with documents to be removed
Do forEach loop to remove the duplicated documents from the collection and store the removed in backup collection just in case you have doubts later.

Find all objects that have duplicated values in Mongo DB

How can I return all objects from a collection where name is same in all objects?
For example in this case name: John
[
{
_id: 1,
name: "John",
last: "Smith"
},
{
_id: 8,
name: "John",
last: "Snow"
},
{
_id: 16,
name: "John",
last: "McKay"
},
]
you can use group in aggregate to return all data that have the same name
db.collection.aggregate([
{
"$group": {
"_id": "$name",
"orig": {
"$push": "$$ROOT"
}
}
},
{
"$addFields": {
"sizeOrig": {
$size: "$orig"
}
}
},
{
"$match": {
sizeOrig: {
$gt: 0
}
}
},
{
$unwind: "$orig"
},
{
"$replaceRoot": {
"newRoot": "$orig"
}
}
])
example : https://mongoplayground.net/p/DfTA6_pUaRA
but if you want just single data for each duplication , you need just do it by group
db.collection.aggregate([
{
"$group": {
"_id": "$name",
"orig": {
"$push": "$$ROOT"
}
}
}
])
https://mongoplayground.net/p/hd1z77cdtp0
you need to use "findAll({})" or just "find({})" with the collection name it will return an array object that will contain all the documents from the collection. For example if you have a collection or a model which is named "Employees" you need to do the following after connection to db.
Employees.find({});
This will return an array with all docs inside Employee collection. Once you have this returned you can iterate over it.

total of all groups totals using mongodb

i did this Aggregate pipeline , and i want add a field contains the Global Total of all groups total.
{ "$match": query },
{ "$sort": cursor.sort },
{ "$group": {
_id: { key:"$paymentFromId"},
items: {
$push: {
_id:"$_id",
value:"$value",
transaction:"$transaction",
paymentMethod:"$paymentMethod",
createdAt:"$createdAt",
...
}
},
count:{$sum:1},
total:{$sum:"$value"}
}}
{
//i want to get
...project groups , goupsTotal , groupsCount
}
,{
"$skip":cursor.skip
},{
"$limit":cursor.limit
},
])
you need to use $facet (avaialble from MongoDB 3.4) to apply multiple pipelines on the same set of docs
first pipeline: skip and limit docs
second pipeline: calculate total of all groups
{ "$match": query },
{ "$sort": cursor.sort },
{ "$group": {
_id: { key:"$paymentFromId"},
items: {
$push: "$$CURRENT"
},
count:{$sum:1},
total:{$sum:"$value"}
}
},
{
$facet: {
docs: [
{ $skip:cursor.skip },
{ $limit:cursor.limit }
],
overall: [
{$group: {
_id: null,
groupsTotal: {$sum: '$total'},
groupsCount:{ $sum: '$count'}
}
}
]
}
the final output will be
{
docs: [ .... ], // array of {_id, items, count, total}
overall: { } // object with properties groupsTotal, groupsCount
}
PS: I've replaced the items in the third pipe stage with $$CURRENT which adds the whole document for the sake of simplicity, if you need custom properties then specify them.
i did it in this way , project the $group result in new field doc and $sum the sub totals.
{
$project: {
"doc": {
"_id": "$_id",
"total": "$total",
"items":"$items",
"count":"$count"
}
}
},{
$group: {
"_id": null,
"globalTotal": {
$sum: "$doc.total"
},
"result": {
$push: "$doc"
}
}
},
{
$project: {
"result": 1,
//paging "result": {$slice: [ "$result", cursor.skip,cursor.limit ] },
"_id": 0,
"globalTotal": 1
}
}
the output
[
{
globalTotal: 121500,
result: [ [group1], [group2], [group3], ... ]
}
]

How to do an aggregate operation with $cond checking for null values in same array while grouping

This is my document,
{
"_id": "58aecaa3758fbff4176db088",
"actualEndDate": "2017-02-27T00:00:00.000Z",
"Details": [
{
"projectId": "58a585f6758fbff4176dadb9",
}
]
},
{
"_id": "58aecac8758fbff4176db08b",
"actualEndDate": "2017-03-12T00:00:00.000Z",
"Details": [
{
"projectId": "58a585f6758fbff4176dadb9",
}
]
},
{
"_id": "58aecac8758fbff4176db08c",
"actualEndDate": null,
"Details": [
{
"projectId": "58a585f6758fbff4176dadb9",
}
]
}
I need to group these and in my query, I need to find $max of "actualEndDate" with condition only if all "actualEndDate" fields are not null.
I tried something like this,
{
$group:
{
_id: '$Details.projectId',
actualEndDate:
{
$cond: {
if: { $ne: [ $actualEndDate, null] },
then: $actualEndDate, else: null
}
}
}
}
so that I can get the expected result like
{
"projectId": "58a585f6758fbff4176dadb9",
"actualEndDate": null,
}
If the sample does not contain the { "_id": "58aecac8758fbff4176db08c" } record, then I expect the result to have actualEndDate as max of other two documents
{
"projectId": "58a585f6758fbff4176dadb9",
"actualEndDate": "2017-03-12T00:00:00.000Z",
}
Any help is appreciated.
You can $sort your actualDate field, $unwind your Details array and then $group taking only the $first date will ensure this is the most recent date:
db.data.aggregate([{
$sort: {
"actualEndDate": -1
}
}, {
$unwind: "$Details"
}, {
$group: {
_id: "$Details.projectId",
actualEndDate: {
$first: "$actualEndDate"
}
}
}]);
If you want to return a null date if at least one date is null for the specified projectId the following will do the job :
db.data.aggregate([{
$sort: {
"actualEndDate": -1
}
}, {
$unwind: "$Details"
}, {
$group: {
_id: "$Details.projectId",
dates: {
$push: "$actualEndDate"
}
}
}, {
$project: {
projectId: "$_id",
actualEndDate: {
$cond: {
if: {
$setIsSubset: [
[null], "$dates"
]
},
then: null,
else: { $arrayElemAt: ["$dates", 0] }
}
}
}
}]);
The idea is to $push all the dates into a new array and make a projection to return null when null is spotted inside the array otherwise return the first element of the array (with the values sorted in the first stage)