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
]
}
}
}
}
])
Related
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
}
}
])
I have below mongodb query, in which i am using $addToSet, Now i want to use it condition vise.
Worksheet.aggregate([
{
"$group": {
"_id": null,
"todayBilling": {
"$sum": {
"$cond": [{ "$and" : [ { "$eq": [ "$isBilling", true] }, { $eq: [ "$date",new Date(moment().format('l'))]}] },"$hours",0 ]
}
},
"todayProjects": { "$addToSet": "$projectId" }
},
},
{ "$addFields": { "todayProjects": { "$size": "$todayProjects" }}},
{
"$lookup":{
"from": "projects",
"let": {},
"pipeline": [
{
"$group": { "_id": null, "count": { "$sum": 1 } }
}
],
"as": "totalProjects"
}
},
{'$unwind':'$totalProjects'}
])
Now, I want to get the count of todayProjects field if got result today date vise. means where "todayProjects": { "$addToSet": "$projectId" } exists, i want to use $cond with below condition:
{ $eq: [ "$date",new Date(moment().format('l'))]}
Below aggregate mongodb query gives
Unexpected token : error
db.getCollection("products_data").aggregate(
{
"$unwind": {
"path": "$color",
"preserveNullAndEmptyArrays": true
}
},
{
"$match":{
"country":"UK",
"$or":[{
"$and":[
"$or":
[{
"$and":[
{"status":"drafted"},
{"color":{$in:["blue"]}}
]},
{"$and":[
{"status1":"complete"},
{"status2":{$nin:["n/a","drafted","complete"]}},
{"color":{$in:["green"]}}
]}
]
]
},{
"$and":[
"$or":
[
{ "$and":[
{"status":"drafted"},
{"color":{$in:["blue"]}}
]},
{"$and":[
{"status1":"complete"},
{"status2":{$nin:["n/a","drafted","complete"]}},
{"color":{$in:["green"]}}
]}
]
]
}
]
}
},
{
"$group":{
"_id":"$field",
"products":{$sum: 1},
"bid":{"$push":"$product_id"}
}
},
{
"$project":{
"field":"$_id",
"products":"$products",
"bid":1,
"_id":0
}
}
);
To fetch the aggregate count for the given specified condition.
Correct syntax to use aggregate and it's stages in pipeline
db.getCollection("products_data").aggregate([
{ "$unwind": { "path": "$color", "preserveNullAndEmptyArrays": true }},
{ "$match": {
"country": "UK",
"$or": [
{
"$and": [
{
"$or": [
{ "$and": [{ "status": "drafted" }, { "color": { "$in": ["blue"] }}] },
{ "$and": [{ "status1": "complete" }, { "status2": { "$nin": ["n/a", "drafted", "complete"] }}, { "color": { "$in": ["green"] }}]}
]
}
]
},
{
"$and": [
{
"$or": [
{ "$and": [{ "status": "drafted" }, { "color": { "$in": ["blue"] }}] },
{ "$and": [{ "status1": "complete" }, { "status2": { "$nin": ["n/a", "drafted", "complete"] }}, { "color": { "$in": ["green"] }}] }
]
}
]
}
]
}},
{ "$group": {
"_id": "$field",
"products": { "$sum": 1 },
"bid": { "$push": "$product_id" }
}},
{ "$project": { "field": "$_id", "products": "$products", "bid": 1, "_id": 0 }}
])
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
}
}
}
}
}
)
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 }