Mongodb plus and minus in one query (subtract two query results) - mongodb

I have the following collection:
[
{
"type": "debit",
"amount": 10
},
{
"type": "debit",
"amount": 20
},
{
"type": "credit",
"amount": 5
},
]
I need to sum all documents with type debit and minus sum of credits.
how can I handle this with mongodb?
so my expected result is 25

db.collection.aggregate([
{
"$group": {
"_id": "$type",
"field": {
$sum: {
"$cond": {
"if": {
"$eq": [
"$type",
"debit"
]
},
"then": "$amount",
"else": {
"$multiply": [
"$amount",
-1
]
}
}
}
}
}
},
{
"$group": {
"_id": null,
"result": {
"$sum": "$field"
}
}
}
])
https://mongoplayground.net/p/cfTixd1Oo_z

Related

How to add a summary header/footer of a dataset within the same query using Mongodb

Let the following dataset (_id ommited for clarity sakes)
{ "model":"Nissan", "regId": 1230, "status": "active", "regCost" :100},
{ "model":"Nissan", "regId": 1231, "status": "active", "regCost" :100 },
{ "model":"Nissan", "regId": 1232, "status": "inactive", "regCost" :0},
{ "model":"Honda", "regId": 1233, "status": "active", "regCost" :90},
{ "model":"Honda", "regId": 1234, "status": "active", "regCost" :90},
{ "model":"Toyota", "regId": 1235, "status": "active", "regCost" :80}
Running the following query in Mongo
[
{
"$group": {
"_id": "$model",
"TotalActive": {
"$sum": {
"$cond": {
"if": {
"$eq": ["$status", "active"]
},
"then": 1,
"else": 0
}
}
},
"TotalCost" : {"$sum" : "$regCost"}
}
}
]
will give this above result:
The question is how can I modify my query in order to add a summary row like:
You can use below aggregation
db.collection.aggregate([
{ "$group": {
"_id": "$model",
"TotalActive": {
"$sum": {
"$cond": {
"if": {
"$eq": ["$status", "active"]
},
"then": 1,
"else": 0
}
}
},
"TotalCost": { "$sum": "$regCost" }
}},
{ "$facet": {
"total": [
{ "$group": {
"_id": "Total",
"TotalActive": { "$sum": "$TotalActive" },
"TotalCost": { "$sum": "$TotalCost" }
}}
],
"data": [{ "$match": {} }]
}},
{ "$project": {
"data": {
"$concatArrays": ["$data", "$total"]
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } }
])
MongoPlayground

Mongodb multi level aggregation

Data in mongo
[{
"_id": "5d71d1432f7c8151c58c4481",
"payment": {
"transactions": [
{
"_id": "5d71d1ff2f7c8151c58c44cf",
"method": "paytm",
"amount": 100,
"paymentOn": "2019-09-06T03:26:44.959Z"
},
{
"_id": "5d71d1ff2f7c8151c58c44ce",
"method": "cash",
"amount": 650,
"paymentOn": "2019-09-06T03:26:55.531Z"
}
],
"status": "partial"
},
"customer": "5d66c434c24f2b1fb6772014",
"order": {
"orderNumber": "WP-ORD-06092019-001",
"total": 770,
"balance": 20
}
},
{
"_id": "5d71d1432f7c8151c58c4481",
"payment": {
"transactions": [
{
"_id": "5d71d1ff2f7c8151c58c44cf",
"method": "paytm",
"amount": 100,
"paymentOn": "2019-09-06T03:26:44.959Z"
}
],
"status": "partial"
},
"customer": "5d66c434c24f2b1fb6772014",
"order": {
"orderNumber": "WP-ORD-06092019-001",
"total": 200,
"balance": 100
}
}]
I want to aggregate payments by method.
So the result would look like below:
Output:
Paytm: 200
Cash : 650
Unpaid(Balance): 120
I have tried:
[
{
'$unwind': {
'path': '$payment.transactions',
'preserveNullAndEmptyArrays': true
}
}, {
'$project': {
'amount': '$payment.transactions.amount',
'method': '$payment.transactions.method'
}
}, {
'$group': {
'_id': '$method',
'amount': {
'$sum': '$amount'
}
}
}
]
But how to include balance calculation as well
Using the above dataset, use the aggregate pipeline for calculation using aggregate as:
db.collection.aggregate([
{
$facet: {
paidAmounts: [
{ '$unwind': { 'path': '$payment.transactions', 'preserveNullAndEmptyArrays': true } },
{
$group: {
_id: "$payment.transactions.method",
amount: {
$sum: "$payment.transactions.amount"
}
}
}
],
leftAmounts: [
{
$group: {
_id: null,
balance: {
$sum: "$order.balance"
}
}
}
]
}
}
])
giving output:
here leftAmounts has left balance and paidAmounts having grouped paid data on basis of payment type
[
{
"leftAmounts": [
{
"_id": null,
"balance": 120
}
],
"paidAmounts": [
{
"_id": "cash",
"amount": 650
},
{
"_id": "paytm",
"amount": 200
}
]
}
]
Working solution : https://mongoplayground.net/p/7IWELKKMsWe
db.collection.aggregate([
{
"$unwind": "$payment.transactions"
},
{
"$group": {
"_id": "$_id",
"balance": {
"$first": "$order.balance"
},
"paytm": {
"$sum": {
"$cond": [
{
"$eq": [
"$payment.transactions.method",
"paytm"
]
},
"$payment.transactions.amount",
0
]
}
},
"cash": {
"$sum": {
"$cond": [
{
"$eq": [
"$payment.transactions.method",
"cash"
]
},
"$payment.transactions.amount",
0
]
}
}
}
},
{
"$group": {
"_id": null,
"balance": {
"$sum": "$balance"
},
"cash": {
"$sum": "$cash"
},
"paytm": {
"$sum": "$paytm"
}
}
}
])

How to get count of multiple fields based on value in mongodb?

Collection exists as below:
[
{"currentLocation": "Chennai", "baseLocation": "Bengaluru"},
{"currentLocation": "Chennai", "baseLocation": "Bengaluru"},
{"currentLocation": "Delhi", "baseLocation": "Bengaluru"},
{"currentLocation": "Chennai", "baseLocation": "Chennai"}
]
Expected Output:
[
{"city": "Chennai", "currentLocationCount": 3, "baseLocationCount": 1},
{"city": "Bengaluru", "currentLocationCount": 0, "baseLocationCount": 3},
{"city": "Delhi", "currentLocationCount": 1, "baseLocationCount": 0}
]
What I have tried is:
db.getCollection('users').aggregate([{
$group: {
"_id": "$baselocation",
baseLocationCount: {
$sum: 1
}
},
}, {
$project: {
"_id": 0,
"city": "$_id",
"baseLocationCount": 1
}
}])
Got result as:
[
{"city": "Chennai", "baseLocationCount": 1},
{"city": "Bengaluru", "baseLocationCount": "3"}
]
I'm not familiar with mongo, so any help?
MongoDB Version - 3.4
Neil Lunn and myself had a lovely argument over this topic the other day which you can read all about here: Group by day with Multiple Date Fields.
Here are two solutions to your precise problem.
The first one uses the $facet stage. Bear in mind, though, that it may not be suitable for large collections because $facet produces a single (potentially huge) document that might be bigger than the current MongoDB document size limit of 16MB (which only applies to the result document and wouldn't be a problem during pipeline processing anyway):
collection.aggregate(
{
$facet:
{
"current":
[
{
$group:
{
"_id": "$currentLocation",
"currentLocationCount": { $sum: 1 }
}
}
],
"base":
[
{
$group:
{
"_id": "$baseLocation",
"baseLocationCount": { $sum: 1 }
}
}
]
}
},
{ $project: { "result": { $setUnion: [ "$current", "$base" ] } } }, // merge results into new array
{ $unwind: "$result" }, // unwind array into individual documents
{ $replaceRoot: { newRoot: "$result" } }, // get rid of the additional field level
{ $group: { "_id": "$_id", "currentLocationCount": { $sum: "$currentLocationCount" }, "baseLocationCount": { $sum: "$baseLocationCount" } } }, // group into final result)
{ $project: { "_id": 0, "city": "$_id", "currentLocationCount": 1, "baseLocationCount": 1 } } // group into final result
)
The second one works based on the $map stage instead:
collection.aggregate(
{
"$project": {
"city": {
"$map": {
"input": [ "current", "base" ],
"as": "type",
"in": {
"type": "$$type",
"name": {
"$cond": {
"if": { "$eq": [ "$$type", "current" ] },
"then": "$currentLocation",
"else": "$baseLocation"
}
}
}
}
}
}
},
{ "$unwind": "$city" },
{
"$group": {
"_id": "$city.name",
"currentLocationCount": {
"$sum": {
"$cond": {
"if": { "$eq": [ "$city.type", "current" ] },
"then": 1,
"else": 0
}
}
},
"baseLocationCount": {
"$sum": {
"$cond": {
"if": { "$eq": [ "$city.type", "base" ] },
"then": 1,
"else": 0
}
}
}
}
}
)

Grouping different amounts together in MongoDB

If I have a set of objects each with the same description, but with different amounts.
{
{
"_id": "101",
"description": "DD from my employer1",
"amount": 1000.33
},
{
"_id": "102",
"description": "DD from my employer1",
"amount": 1000.34
},
{
"_id": "103",
"description": "DD from my employer1",
"amount": 1000.35
},
{
"_id": "104",
"description": "DD from employer1",
"amount": 5000.00
},
{
"_id": "105",
"description": "DD from my employer2",
"amount": 2000.33
},
{
"_id": "106",
"description": "DD from my employer2",
"amount": 2000.33
},
{
"_id": "107",
"description": "DD from my employer2",
"amount": 2000.33
}
}
Below, I am able to group them using the description:
{
{
"$group": {
"_id": {
"description": "$description"
},
"count": {
"$sum": 1
},
"_id": {
"$addToSet": "$_id"
}
}
},
{
"$match": {
"count": {
"$gte": 3
}
}
}
}
Is there a way to include all the amounts in the group (_ids: 101, 102, and 103 plus 105,106,107) even if they have a small difference, but exclude the bonus amount, which in the sample above is _id 104?
I don't believe it could be done in a group stage, but is there something that could be done at a later stage that could group _ids 101, 102 and 103 together and exclude _id 104. Basically, I want MongoDB to ignore the small differences in 101, 102, 103 and group them together since the are paychecks coming from the same employer.
I have been working with $stdDevPop, but can't get a solid formula down.
I am looking for a simple array output of just the _ids.
{
"result": [
"101",
"102",
"103",
"105",
"106",
"107"
]
}
You can do this by doing some math on the "amount" to round it down to the nearest 1000 and use that as the grouping _id:
db.collection.aggregate([
{ "$group": {
"_id": {
"$subtract": [
{ "$trunc": "$amount" },
{ "$mod": [
{ "$trunc": "$amount" },
1000
]}
]
},
"results": { "$push": "$_id" }
}},
{ "$redact": {
"$cond": {
"if": { "$gt": [ { "$size": "$results" }, 1 ] },
"then": "$$KEEP",
"else": "$$PRUNE"
}
}},
{ "$unwind": "$results" },
{ "$group": {
"_id": null,
"results": { "$push": "$results" }
}}
])
If your MongoDB is older than 3.2 then you would just need to use a long form with $mod of what $trunc is doing. And if your MongoDB is older than 2.6 then rather than $redact you would $match. So in the longer form this is:
db.collection.aggregate([
{ "$group": {
"_id": {
"$subtract": [
{ "$subtract": [
"$amount",
{ "$mod": [ "$amount", 1 ] }
]},
{ "$mod": [
{ "$subtract": [
"$amount",
{ "$mod": [ "$amount", 1 ] }
]},
1000
]}
]
},
"results": { "$push": "$_id" },
"count": { "$sum": 1 }
}},
{ "$match": { "count": { "$gt": 1 } } },
{ "$unwind": "$results" },
{ "$group": {
"_id": null,
"results": { "$push": "$results" }
}}
])
Either way the output is just the _id values whose amounts grouped to the boundaries with a count more than once.
{ "_id" : null, "results" : [ "105", "106", "107", "101", "102", "103" ] }
You could either add a $sort in there or live with sorting the result array in client code.
db.yourDBNameHere.aggregate( [
{ $match: { "amount" : { $lt : 5000 } } },
{ $project: { _id: 1 } },
])
that will grab the ID only of every transaction less than 5000$.

Mongodb Muliple averages based on a different column

Using mongodb aggregate, is there a way to have the query return Weight average on all Scale , an average of Scale 1 , an average of Scale 2 all all returned in the same query?
This is an example of an entry in my data set
{
"Profile" : "P1",
"AvgWeight" : 639,
"Time" : "2017-04-14T05:17:42.000Z",
"Scale" : 1,
"Weight" : 1504,
"Target" : 680
}
My query that I am currently running that is averaging the weight accrost all scales ( Might not help, but better to have more info )
[{
"$match": {
"Time": {
"$gt": moment(start).format("YYYY-MM-DD HH:mm:ss"),
"$lt": moment(end).format("YYYY-MM-DD HH:mm:ss")
}
}
},
{
"$group": {
"_id": {
"hour": {
"$hour": "$Time"
},
"day": {
"$dayOfYear": "$Time"
},
"interval": {
"$add": [{
"$multiply": [{
"$minute": "$Time"
}]
},
{
"$multiply": [{
"$hour": "$Time"
},
100
]
},
{
"$multiply": [{
"$dayOfYear": "$Time"
},
10000
]
},
{
"$multiply": [{
"$year": "$Time"
},
10000000
]
}
]
}
},
"time": {
"$first": "$Time"
},
"avgW": {
"$avg": "$AvgWeight"
},
"avgWe": {
"$avg": "$Weight"
},
"avgTarget": {
"$avg": "$Target"
}
}
}, {
"$sort": {
"Time": -1
}
}
]
Adding Expected Response SOmething like
[
{
"_id : {"hour":1,"day":105,"interval":20971050122},
"time":"2017-04-15T01:22:58.000Z",
"avgW":646,
"avgWe":1577,
"avgTarget":680 ,
"Scale1" : 100 ,
"Scale2" : 120
} ,
{ "_id":{"hour":1,"day":105,"interval":20771050122},
"time":"2017-04-15T01:22:55.000Z",
"avgW":646,
"avgWe":1335,
"avgTarget":680 ,
"Scale1" : 100 ,
"Scale2" : 120 }
]
But if it is a little different I can handle it, as long all the scales are in the same parent object ( it would be to cpu intensive to post process them to link up the matching groups )
You can split the first group into two groups.
First group to calculate weight avg for all scales and second group to do rest of avgs.
Something like:
[{
"$match": {
"Time": {
"$gt": moment(start).format("YYYY-MM-DD HH:mm:ss"),
"$lt": moment(end).format("YYYY-MM-DD HH:mm:ss")
}
}
}, {
"$group": {
"_id": {
"scale": "$Scale",
"hour": {
"$hour": "$Time"
},
"day": {
"$dayOfYear": "$Time"
},
"interval": {
"$add": [{
"$multiply": [{
"$minute": "$Time"
}]
},
{
"$multiply": [{
"$hour": "$Time"
},
100
]
},
{
"$multiply": [{
"$dayOfYear": "$Time"
},
10000
]
},
{
"$multiply": [{
"$year": "$Time"
},
10000000
]
}
]
}
},
"time": {
"$first": "$Time"
},
"scaleAvg: {
"$avg": "$Weight"
}
}
}, {
"$group": {
"_id": {
"hour": "$_id.hour",
"day": "$_id.day",
"interval": "$_id.interval"
},
"time": {
"$first": "$time"
},
"avgW": {
"$avg": "$AvgWeight"
},
"avgWe": {
"$avg": "$Weight"
},
"avgTarget": {
"$avg": "$Target"
},
"scaleAvgs": {
"$push": {
"scale": "$_id.scale",
"scaleAvg": "$scaleAvg"
}
}
}
}, {
"$sort": {
"time": -1
}
}]