MongoDB group by and SUM by array - mongodb

I'm new in mongoDB.
This is one example of record from collection:
{
supplier: 1,
type: "sale",
items: [
{
"_id": ObjectId("60ee82dd2131c5032342070f"),
"itemBuySum": 10
},
{
"_id": ObjectId("60ee82dd2131c50323420710"),
"itemBuySum": 10,
},
{
"_id": ObjectId("60ee82dd2131c50323420713"),
"itemBuySum": 10
},
{
"_id": ObjectId("60ee82dd2131c50323420714"),
"itemBuySum": 20
}
]
}
I need to group by TYPE field and get the SUM. This is output I need:
{
supplier: 1,
sales: 90,
returns: 170
}
please check Mongo playground for better understand. Thank you!

$match - Filter documents.
$group - Group by type and add item into data array which leads to the result like:
[
[/* data 1 */],
[/* data 2 */]
]
$project - Decorate output document.
3.1. First $reduce is used to flatten the nested array to a single array (from Result (2)) via $concatArrays.
3.2. Second $reduce is used to aggregate $sum the itemBuySum.
db.collection.aggregate({
$match: {
supplier: 1
},
},
{
"$group": {
"_id": "$type",
"supplier": {
$first: "$supplier"
},
"data": {
"$push": "$items"
}
}
},
{
"$project": {
_id: 0,
"supplier": "$supplier",
"type": "$_id",
"returns": {
"$reduce": {
"input": {
"$reduce": {
input: "$data",
initialValue: [],
in: {
"$concatArrays": [
"$$value",
"$$this"
]
}
}
},
"initialValue": 0,
"in": {
$sum: [
"$$value",
"$$this.itemBuySum"
]
}
}
}
}
})
Sample Mongo Playground

db.collection.aggregate([
{
$match: {
supplier: 1
},
},
{
"$group": {
"_id": "$ID",
"supplier": {
"$first": "$supplier"
},
"sale": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$type",
"sale"
]
},
"then": {
"$sum": "$items.itemBuySum"
},
"else": {
"$sum": 0
}
}
}
},
"returns": {
"$sum": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$type",
"return"
]
},
"then": {
"$sum": "$items.itemBuySum"
},
"else": {
"$sum": 0
}
}
}
}
}
}
},
{
"$project": {
_id: 0,
supplier: 1,
sale: 1,
returns: 1
}
}
])

Related

Query Mongodb using condition from previous field defined via $first

Trying to total a documents by a condition where timestamp equals the first timestamp found
Need to sum the number of documents that meet the condition that the document matched the first timestamp value found. Below is what I've tried in order to reference the timestamp equal to { "$first": "$timestamp" }
IP.aggregate([
{
"$sort":{'timestamp':-1}
},
{
"$group": {
"_id": "$application",
"url": { "$first": "$app_url_name" },
"timestamp": { "$first": "$timestamp" },
"total": {
$sum: {
$cond: {
if: {
$and:[
{$eq: ['$environment_category', 'PROD']},
{$eq: ['$timestamp', '$_id.timestamp']}
]
},
then: 1,
else: 0
}
}
},
"enabled": { $sum: {$cond: {if: {
$and: [
{$eq: ['$availability', 'available']},
{$eq: ['$state', 'enabled']},
{$eq: ['$environment_category', 'PROD']}
]
} ,
then: 1,
else: 0} }}
}
},
Here's the solution:
db.ips.aggregate([
{
"$sort": { "timestamp": -1 }
},
{
"$group": {
"_id": "$application",
"doc": { "$first": "$$ROOT" },
"items": {"$push": "$$ROOT"}
}
},
{
"$project": {
"_id": 0,
"application": "$_id",
"timestamp": "$doc.timestamp",
"items": {
"$filter": {
"input": "$items",
"as": "item",
"cond": { "$eq": [ "$$item.timestamp", "$doc.timestamp" ] }
}
}
}
},
]);

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
}
}
}
}
}
)

MongoDB: error with $divide and $multiply

I'm creating a MongoDB aggregation pipeline and I'm stuck at this stage:
$group: {
_id: {checkType: "$_id.checkType", resultCode: "$_id.resultCode"},
count: { $sum: "$count" },
ctv: { $sum: "$ctv" },
perc:{$multiply:[{$divide:["$ctv","$count"]},100]},
weight: { $divide: [ "$ctv", "$count"] },
details: { $push: "$$ROOT" }
}
It gives the error "The $multiply accumulator is a unary operator". Similarly if I remove the line with $multiply I get "The $divide accumulator is a unary operator" on the subsequent line. I cannot find a description for this error on the Net. What's wrong in my sintax?
The arithmetic operators cannot be used as $group accumulators. Move them to another $project pipeline stage as:
db.collection.aggregate([
{ "$group": {
"_id": { "checkType": "$_id.checkType", "resultCode": "$_id.resultCode" },
"count": { "$sum": "$count" },
"ctv": { "$sum": "$ctv" },
"details": { "$push": "$$ROOT" }
} },
{ "$project": {
"count": 1,
"details": 1,
"ctv": 1,
"perc": { "$multiply": [ { "$divide": ["$ctv","$count"] }, 100 ] },
"weight": { "$divide": ["$ctv", "$count"] },
} }
])
or
if using MongoDB 3.4 and above, use $addFields instead of $project
db.collection.aggregate([
{ "$group": {
"_id": { "checkType": "$_id.checkType", "resultCode": "$_id.resultCode" },
"count": { "$sum": "$count" },
"ctv": { "$sum": "$ctv" },
"details": { "$push": "$$ROOT" }
} },
{ "$addFields": {
"perc": { "$multiply": [ { "$divide": ["$ctv","$count"] }, 100 ] },
"weight": { "$divide": ["$ctv", "$count"] },
} }
])

Combine three different Aggregate into a single one

I have three different Aggregations that I would like to combine to one.
objsCount = self.db.bidding.aggregate([
{ "$group": {
"_id": "$bid_item",
"number_of_objects": { "$sum": 1 }
}}
])
objsPrice = self.db.bidding.aggregate([
{"$match": {"$and" : [{"price": {"$ne":"null"}},{"users":{"$ne":"null"}}]}},
{ "$group": {
"_id": "$bid_item",
"total_price": { "$sum": '$price' },
"total_users": { "$sum": '$users' }
}}
])
objsHigestBid = self.db.bidding.aggregate([
{"$match": {"$and" : [{"highestBid": {"$ne":"null"}},{"users":{"$ne":"null"}}]}},
{ "$group": {
"_id": "$bid_item",
"total_number_of_users_after_bid": { "$sum": '$highestBid' }
}}
])
The output I'm looking for is:
[{ _id : '',
number_of_objects : '',
total_price : '',
total_users : '',
total_number_of_users_after_bid : ''
},...]
How could I change my "aggregates" to one single "aggregate" that make this possible?
With a single $group pipeline, you can take advantage of the $cond operator in the pipeline step to evaluate the counts based on the logic specified, something like the following:
db.bidding.aggregate([
{
"$group": {
"_id": "$bid_item",
"number_of_objects": { "$sum": 1 },
"total_price": {
"$sum": {
"$cond": [ { "$ne": [ "$price", "null" ] }, "$price", 0 ]
}
},
"total_users": {
"$sum": {
"$cond": [ { "$ne": [ "$users", "null" ] }, "$users", 0 ]
}
},
"total_number_of_users_after_bid": {
"$sum": {
"$cond": [
{
"$and": [
{ "$ne": [ "$highestBid", "null" ] },
{ "$ne": [ "$users", "null" ] }
]
},
"$highestBid", 0
]
}
}
}
}
])

Mongo Aggregation : $group and $project array to object for counts

I have documents like:
{
"platform":"android",
"install_date":20151029
}
platform - can have one value from [android|ios|kindle|facebook ] .
install_date - there are many install_dates
There are also many fields.
Aim : I am calculating installs per platform on particular date.
So I am using group by in aggregation framework and make counts by platform. Document should look like like:
{
"install_date":20151029,
"platform" : {
"android":1000,
"ios": 2000,
"facebook":1500
}
}
I have done like:
db.collection.aggregate([
{
$group: {
_id: { platform: "$platform",install_date:"$install_date"},
count: { "$sum": 1 }
}
},
{
$group: {
_id: { install_date:"$_id.install_date"},
platform: { $push : {platform :"$_id.platform", count:"$count" } }
}
},
{
$project : { _id: 0, install_date: "$_id.install_date", platform: 1 }
}
])
which Gives document like:
{
"platform": [
{
"platform": "facebook",
"count": 1500
},
{
"platform": "ios",
"count": 2000
},
{
"platform": "android",
"count": 1000
}
],
"install_date": 20151027
}
Problem:
Projecting array to single object as "platform"
With MongoDb 3.4 and newer, you can leverage the use of $arrayToObject operator to get the desired result. You would need to run the following aggregate pipeline:
db.collection.aggregate([
{ "$group": {
"_id": {
"date": "$install_date",
"platform": { "$toLower": "$platform" }
},
"count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.date",
"counts": {
"$push": {
"k": "$_id.platform",
"v": "$count"
}
}
} },
{ "$addFields": {
"install_date": "$_id",
"platform": { "$arrayToObject": "$counts" }
} },
{ "$project": { "counts": 0, "_id": 0 } }
])
For older versions, take advantage of the $cond operator in the $group pipeline step to evaluate the counts based on the platform field value, something like the following:
db.collection.aggregate([
{ "$group": {
"_id": "$install_date",
"android_count": {
"$sum": {
"$cond": [ { "$eq": [ "$platform", "android" ] }, 1, 0 ]
}
},
"ios_count": {
"$sum": {
"$cond": [ { "$eq": [ "$platform", "ios" ] }, 1, 0 ]
}
},
"facebook_count": {
"$sum": {
"$cond": [ { "$eq": [ "$platform", "facebook" ] }, 1, 0 ]
}
},
"kindle_count": {
"$sum": {
"$cond": [ { "$eq": [ "$platform", "kindle" ] }, 1, 0 ]
}
}
} },
{ "$project": {
"_id": 0, "install_date": "$_id",
"platform": {
"android": "$android_count",
"ios": "$ios_count",
"facebook": "$facebook_count",
"kindle": "$kindle_count"
}
} }
])
In the above, $cond takes a logical condition as it's first argument (if) and then returns the second argument where the evaluation is true (then) or the third argument where false (else). This makes true/false returns into 1 and 0 to feed to $sum respectively.
So for example, if { "$eq": [ "$platform", "facebook" ] }, is true then the expression will evaluate to { $sum: 1 } else it will be { $sum: 0 }