MongoDB merge $facet results with corresponding items - mongodb

Hello I have a collection like this
"_id" :"601bd0f4be72d839303adcd3",
"title":"Payment-1",
"initialBalance": {"$numberDecimal": "75"},
"paymentHistory":[
{"_id": "601bd1542df40f2ca8a769df","payment": {"$numberDecimal": "10"}},
{"_id": "601bd1542df40f2ca8a769de","payment": {"$numberDecimal": "20"}},
]
I want to calculate active balance (initialBalance - total of paymentHistory) for each payment.
I calculated total payments from paymentHistory for each document in collection.
this.aggregate([
{$match:{...}},
{
$facet:{
info:[
{$project:{_id:1,title:1,initialBalance:1}}
],
subPayments:[
{$unwind:"$paymentHistory"},
{$group:{_id:"$_id",total:{$sum:"$paymentHistory.payment"}}},
]
}
}
])
I get this result for above query.
"info": [
{
"_id": "601bd0f4be72d839303adcd3",
"title": "Payment-1",
"initialBalance": {"$numberDecimal": "580"},
},
...
],
"subPayments": [
{
"_id": "601bd0f4be72d839303adcd3",
"total": {"$numberDecimal": "80.75"}
},
...
]
I added following lines to aggregation.
{$facet:{...}},
{$project: {payments:{$setUnion:['$info','$subPayments']}}},
{$unwind: '$payments'},
{$replaceRoot: { newRoot: "$payments" }},
Now, I get this result
{
"_id": "601bd0f4be72d839303adcd3",
"total": {"$numberDecimal": "80.75"}
},
{
"_id": "601bd0f4be72d839303adcd3",
"title": "Payment-1",
"initialBalance": {"$numberDecimal": "580"},
},
{...}
I think if I group them via _id, then I calculate activeBalance in $project aggregation.
...
{
$group:{
_id:"$_id",
title:{$first:"$title"},
initialBalance:{$first:"$initialBalance"},
totalPayment:{$first: "$total"},
}
},
{
$set:{activeBalance:{$subtract:[{$ifNull:["$initialBalance",0]}, {$ifNull:["$totalPayment",0]}]}}
}
The problem is after $group aggregation fields return null.
{
"_id": "601bd0f4be72d839303adcd3",
"title": null,
"initialBalance": null,
"totalPayment": {
"$numberDecimal": "80.75"
},
"activeBalance": {
"$numberDecimal": "-80.75"
}
}
How can I solve this problem?

This is what #prasad_ suggested:
db.accounts.aggregate([
{
$addFields: {
activeBalance: {
$subtract: [
"$initialBalance",
{
$reduce: {
input: "$paymentHistory",
initialValue: 0,
in: { $sum: ["$$value", "$$this.payment"] }
}
}
]
}
}
}
]);

Related

How To Count Values Inside Deeply Nested Array Of Objects in Mongodb Aggregation

I want to sum of values inside array of objects which have another array of objects.
In my case; how can I count 'url' values in all documents inside 'urls' array under 'iocs' array;
Mongo playground: open
Here is document example;
[
{
"_id": {
"$oid": "63b4993d0625ebe8b6f5b06e"
},
"iocs": [
{
"urls": [
{
"url": "7.1.5.2",
}
],
},
{
"urls": [
{
"url": "https://l-ink.me/GeheimeBegierde",
},
{
"url": "GeheimeBegierde.ch",
}
],
},
{
"urls": [
{
"url": "https://l-ink.me/GeheimeBegierde",
}
],
}
],
type: "2"
},
{
"_id": {
"$oid": "63b4993d0624ebe8b6f5b06e"
},
"iocs": [
{
"urls": [
{
"url": "7.1.5.2",
}
],
},
{
"urls": [
{
"url": "https://l-ink.me/GeheimeBegierde",
},
{
"url": "GeheimeBegierde.ch",
}
],
},
{
"urls": [
{
"url": "https://l-ink.me/GeheimeBegierde",
}
],
}
],
type: "3"
},
{
"_id": {
"$oid": "63b4993d0615ebe8b6f5b06e"
},
"iocs": [
{
"urls": [
{
"url": "www.google.com",
}
],
},
{
"urls": [
{
"url": "abc.xyz",
},
{
"url": "GeheimeBegierde.ch",
}
],
},
{
"urls": [
{
"url": "https://123.12",
}
],
}
],
type: "1"
}
]
expected output be like;
url: "7.1.5.2",
count:2,
types:[2,3]
url: "https://l-ink.me/GeheimeBegierde",
count:4,
types:[2,3],
url: "abc.xyz",
count:1,
types:[1],
I tried unwind iocs then project urls but can't figure out how to get this output. I think i must use group but how ? Newbie in mongodb.
Any help would be appreciated. Thanks all.
NOTE: All the answers are working. Thank you all for the contributing.
You could do something like this !
db.collection.aggregate([
{
"$unwind": "$iocs"
},
{
"$unwind": "$iocs.urls"
},
{
"$group": {
"_id": "$iocs.urls.url",
"count": {
"$sum": 1
},
"types": {
"$addToSet": "$type"
}
}
},
{
"$project": {
url: "$_id",
_id: 0,
types: 1,
count: 1
}
},
])
https://mongoplayground.net/p/hhMqh2zI_SX
Here's one way you could do it.
db.collection.aggregate([
{"$unwind": "$iocs"},
{"$unwind": "$iocs.urls"},
{
"$group": {
"_id": "$iocs.urls.url",
"count": {"$count": {}},
"types": {"$addToSet": {"$toInt": "$type"}}
}
},
{
"$set": {
"url": "$_id",
"_id": "$$REMOVE"
}
}
])
Try it on mongoplayground.net.
You can try this query:
Double $unwind to deconstruct the nested array.
Then group by url get the count using $sum nad add the types into a set (to avoid duplicates, otherwise you can use simply $push)
db.collection.aggregate([
{
"$unwind": "$iocs"
},
{
"$unwind": "$iocs.urls"
},
{
"$group": {
"_id": "$iocs.urls.url",
"count": {
"$sum": 1
},
"types": {
"$addToSet": "$type"
}
}
}
])
Example here
Since $unwind is considered an inefficient operation, another option is to use $reduce and only $unwind once:
db.collection.aggregate([
{$project: {
type: 1,
urls: {
$reduce: {
input: "$iocs",
initialValue: [],
in: {$concatArrays: ["$$value", "$$this.urls.url"]}
}
}
}},
{$unwind: "$urls"},
{$group: {
_id: "$urls",
type: {$addToSet: "$type"},
count: {$sum: 1}
}},
{$project: {url: "$_id", count: 1, type: 1}}
])
See how it works on the playground example

MongoDB - Sum the field in an array

How can I get all the sum of fields in an array in Mongoose?
I want to sum up all the amounts in the payments array.
DB:
[
{
"_id": 0,
"name": "shoe",
"payments": [
{
"type": "a",
"amount": 10
},
{
"type": "b",
"amount": 15
},
{
"type": "a",
"amount": 15
},
]
},
{
"_id": 0,
"name": "shirt",
"payments": [
{
"type": "a",
"amount": 5
},
{
"type": "b",
"amount": 20
},
]
}
]
Expected result:
{
"amountSum": 65
}
There is a shorter and most likely faster solution:
db.collection.aggregate([
{
$group: {
_id: null,
amountSum: { $sum: { $sum: "$payments.amount" } }
}
}
])
$group - Group all documents.
1.1. $sum - Sum the value returned from 1.1.1 for the amountSum field.
1.1.1. $reduce - As payments is an array of objects, sum all the amount for the elements and transform the result from the array to number.
db.collection.aggregate([
{
$group: {
_id: null,
amountSum: {
$sum: {
$reduce: {
input: "$payments",
initialValue: 0,
in: {
$sum: [
"$$value",
"$$this.amount"
]
}
}
}
}
}
}
])
Demo # Mongo Playground

Min nested array in mongo

How can I use the $min function to get the min value within nested arrays (and add it to the document)?
[
{
"_id": "a357e77f-a76a-4bc2-8765-923280663e97",
"customers": [
{
"_id": "97170117-4660-4c6f-b8da-2b34d4d0c9ce",
"orders": [
{
"amount": 0.5
},
{
"amount": 6.400001525878906
}
]
},
{
"_id": "7b9ccf5b-3acb-4ed1-8df4-e3b5afc49cba",
"orders": [
{
"amount": 27.29999542236328
},
{
"amount": 0.29999542236328125
}
]
}
]
},
{
"_id": "58433224-8162-4f0a-8168-bc11b4306b0a",
"customers": [
{
"_id": "8a6055d0-9b94-40be-8f96-8fd9088d24aa",
"orders": [
{
"amount": 19.700000762939453
}
]
},
{
"_id": "a50a57b8-61e7-4727-a15a-4a4137b2f81a",
"orders": [
{
"amount": 43.80000305175781
}
]
}
]
}
]
How can I get the min amount value within the $customers.orders.amount path?
I've tried but it returns 0.
db.collection.aggregate([
{
$addFields: {
"amount": {
$sum: "$customers.orders.amount"
}
}
}
])
You can do as below for each customer
playground
db.collection.aggregate([
{//Destruct
"$unwind": "$customers"
},
{//Destruct
"$unwind": "$customers.orders"
},
{//Group by customer id,
$group: {
"_id": "$customers._id",
min: {
$push: {
"$min": "$customers.orders.amount"
}
}
}
}
])
You can use group by null if you want to find min across all the customers.
Found a solution using reduce to create a flat array of the nested arrays and then use $min on that. MongoPlayground
db.collection.aggregate([
{
"$addFields": {
"min": {
$min: {
"$reduce": {
"input": "$customers",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
"$$this.orders.amount"
]
}
}
}
}
}
}
])

MongoDB: Assign document objects to field in '$project' stage

I have a user collection:
[
{"_id": 1,"name": "John", "age": 25, "valid_user": true}
{"_id": 2, "name": "Bob", "age": 40, "valid_user": false}
{"_id": 3, "name": "Jacob","age": 27,"valid_user": null}
{"_id": 4, "name": "Amelia","age": 29,"valid_user": true}
]
I run a '$facet' stage on this collection. Checkout this MongoPlayground.
I want to talk about the first output from the facet stage. The following is the response currently:
{
"user_by_valid_status": [
{
"_id": false,
"count": 1
},
{
"_id": true,
"count": 2
},
{
"_id": null,
"count": 1
}
]
}
However, I want to restructure the output in this way:
"analytics": {
"invalid_user": {
"_id": false
"count": 1
},
"valid_user": {
"_id": true
"count": 2
},
"user_with_unknown_status": {
"_id": null
"count": 1
}
}
The problem with using a '$project' stage along with 'arrayElemAt' is that the order may not be definite for me to associate an index with an attribute like 'valid_users' or others. Also, it gets further complicated because unlike the sample documents that I have shared, my collection may not always contain all the three categories of users.
Is there some way I can do this?
You can use $switch conditional operator,
$project to show value part in v with _id and count field as object, k to put $switch condition
db.collection.aggregate([
{
"$facet": {
"user_by_valid_status": [
{
"$group": {
"_id": "$valid_user",
"count": { "$sum": 1 }
}
},
{
$project: {
_id: 0,
v: { _id: "$_id", count: "$count" },
k: {
$switch: {
branches: [
{ case: { $eq: ["$_id", null] }, then: "user_with_unknown_status" },
{ case: { $eq: ["$_id", false] }, then: "invalid_user" },
{ case: { $eq: ["$_id", true] }, then: "valid_user" }
]
}
}
}
}
],
"users_above_30": [{ "$match": { "age": { "$gt": 30 } } }]
}
},
$project stage in root, convert user_by_valid_status array to object using $arrayToObject
{
$project: {
analytics: { $arrayToObject: "$user_by_valid_status" },
users_above_30: 1
}
}
])
Playground

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], ... ]
}
]