Combine two mongo aggregate query results into one - mongodb

I have two mongo aggregate pipelines that output results. Now I want combine these two pipelines to have a singular output.
Please find below sample collection.
[
{
_id: "ddfdfdfdggfgfgsg",
rate: "3323",
quantity_packs: "343",
shop_name: "Whole Foods",
sku: "20"
manufacturer_name: "Unilever"
},
{
_id: "ddfdfdfsdsds",
rate: "434",
quantity_packs: "453",
shop_name: "Carrefour",
sku: "200"
manufacturer_name: "Unilever"
},
{
_id: "dfdfdgcvgfgfvvv",
rate: "343",
quantity_packs: "23",
shop_name: "Target",
manufacturer_name: "Beirsdorf"
sku: "34"
}
]
Please find below my queries.
First Query
db.collection.aggregate([
{
$match: {
manufacturer_name: {
$in: [ "unilever" ]
}
}
},
{
$group: {
_id: {
"Shop Name": "$shop_name"
},
"total_sku": {
"$addToSet": "$sku"
},
"annual_cost": {
$sum: {
$cond: [
{
$eq: ["$manufacturer_name", "unilever"]
},
{
"$toDouble": "$rate"
},
0
]
}
},
"annual_qty": {
$sum: {
"$toDouble": "$annual_qty"
}
}
}
},
{
$project: {
"sku count": {
"$size": "$total_sku"
},
"Annual Cost WO GST": {
$multiply: [ "$annual_cost", "$annual_qty" ]
},
}
},
])
Result of First Query
[
{
_id: { 'Hospital Name': '7AM mart' },
'sku count': 29,
'Annual Cost WO GST': 79968887.67999999
},
{
_id: { 'Shop Name': 'Apex' },
'sku count': 20,
'Annual Cost WO GST': 1779192666.96
}
]
Second Query
db.collection.aggregate([
{
$match: {
$expr: {
$ne: ["$manufacturer_name", "unilever"]
}
}
},
{
$group: {
_id: {
"Shop Name": "$shop_name"
},
"annual_cost_wo_gst_wo_manu": {
$sum: {
"$toDouble": "$rate"
}
},
"annual_qty": {
$sum: {
"$toDouble": "$annual_qty"
}
}
}
},
{
$project: {
"Ann Cost For Other Manufacturers": {
$multiply: ["$annual_cost_wo_gst_wo_manu", "$annual_qty"]
},
}
}
])
Result of Second Query
[
{
_id: { 'Hospital Name': 'Apex' },
'Ann Cost For Other Manufacturers': 25246715130525.273
},
{
_id: { 'Hospital Name': '7AM Mart' },
'Ann Cost For Other Manufacturers': 1347701834351.495
}
]
As mentioned above, I somehow want to combine to results by correctly mapping the items.
Intended Result
[
{
_id: { 'Hospital Name': '7AM mart' },
'sku count': 29,
'Annual Cost WO GST': 79968887.67999999
'Ann Cost For Other Manufacturers': 1347701834351.495
},
{
_id: { 'Shop Name': 'Apex' },
'sku count': 20,
'Annual Cost WO GST': 1779192666.96
'Ann Cost For Other Manufacturers': 25246715130525.273
}
]

Your 2 queries do not quite produce your stated outputs. Nevertheless, you could first perform uncorrelated $lookup to perform your second query, storing the result of your secondary query in a field/object. Then you can continue your first query. Finally extract the result of secondary query from the previously stored field/object.
Here is a Mongo playground with some modifications to your original examples for your reference.

Related

MongoDB aggregate $group $sum that matches date inside array of objects

I'll explain my problem here and i'll put a tldr at the bottom summarizing the question.
We have a collection called apple_receipt, since we have some apple purchases in our application. That document has some fields that we will be using on this aggregation. Those are: price, currency, startedAt and history. Price, currency and startedAt are self-explanatory. History is a field that is an array of objects containing a price and startedAt. So, what we are trying to accomplish is a query that gets every document between a date of our choice, for example: 06-06-2020 through 10-10-2022 and get the total price combined of all those receipts that have a startedAt between that. We have a document like this:
{
price: 12.9,
currency: 'BRL',
startedAt: 2022-08-10T16:23:42.000+00:00
history: [
{
price: 12.9,
startedAt: 2022-05-10T16:23:42.000+00:00
},
{
price: 12.9,
startedAt: 2022-06-10T16:23:42.000+00:00
},
{
price: 12.9,
startedAt: 2022-07-10T16:23:42.000+00:00
}
]
}
If we query between dates 06-06-2022 to 10-10-2022, we would have a return like this: totalPrice: 38,7.
-total price of the 3 objects that have matched the date inside that value range-
I have tried this so far:
AppleReceipt.aggregate([
{
$project: {
price: 1,
startedAt: 1,
currency: 1,
history: 1,
}
},
{
$unwind: {
path: "$history",
preserveNullAndEmptyArrays: true,
}
},
{
$match: {
$or: [
{ startedAt: {$gte: new Date(filters.begin), $lt: new Date(filters.end)} },
]
}
},
{
$group: {
_id: "$_id",
data: { $push: '$$ROOT' },
totalAmountHelper: { $sum: '$history.price' }
}
},
{
$unwind: "$data"
},
{
$addFields: {
totalAmount: { $add: ['$totalAmountHelper', '$data.price'] }
}
}
])
It does bring me the total value but I couldn't know how to take into consideration the date to make the match stage to only get the sum of the documents that are between that date.
tl;dr: Want to make a query that gets the total sum of the prices of all documents that have startedAt between the dates we choose. Needs to match the ones inside history field - which is an array of objects, and also the startedAt outside of the history field.
https://mongoplayground.net/p/lOvRbX24QI9
db.collection.aggregate([
{
$set: {
"history_total": {
"$reduce": {
"input": "$history",
"initialValue": 0,
"in": {
$sum: [
{
"$cond": {
"if": {
$and: [
{
$gte: [
new Date("2022-06-06"),
{
$dateFromString: {
dateString: "$$this.startedAt"
}
}
]
},
{
$lt: [
{
$dateFromString: {
dateString: "$$this.startedAt"
}
},
new Date("2022-10-10")
]
},
]
},
"then": "$$this.price",
"else": 0
}
},
"$$value",
]
}
}
}
}
},
{
$set: {
"history_total": {
"$sum": [
"$price",
"$history_total"
]
}
}
}
])
Result:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"currency": "BRL",
"history": [
{
"price": 12.9,
"startedAt": "2022-05-10T16:23:42.000+00:00"
},
{
"price": 12.9,
"startedAt": "2022-06-10T16:23:42.000+00:00"
},
{
"price": 12.9,
"startedAt": "2022-07-10T16:23:42.000+00:00"
}
],
"history_total": 325.79999999999995,
"price": 312.9,
"startedAt": "2022-08-10T16:23:42.000+00:00"
}
]
Kudos goes to #user20042973

Mongodb- group an array by key

I have an array field (contains objects) in multiple documents, I want to merge the arrays into one array and group the array by object key. I have manage to group the array but I dont know how to group the data. See the code I tried below
const test = await salesModel.aggregate([
{ $unwind: "$items" },
{
$group: {
_id: 0,
data: { $addToSet: '$items' }
},
}
])
Result of the query:
{
_id: 0,
data: [
{
_id: 61435b3c0f773abaf77a367e,
price: 3000,
type: 'service',
sellerId: 61307abca667678553be81cb,
},
{
_id: 613115808330be818abaa613,
price: 788,
type: 'product',
sellerId: 61307abca667678553be81cb,
},
{
_id: 61307c1ea667676078be81cc,
price: 1200,
type: 'product',
sellerId: 61307abca667678553be81cb,
}
]
}
Now I want to group the data array by object key data.sellerId and sum price
Desired Output:
{
data: [
{
sumPrice: 788,
sellerId: 613115808330be818abaa613,
},
{
sumPrice: 1200,
sellerId: 61307abca667678553be81cb,
}
]
}
Extend with the current query and result with:
$unwind: Deconstruct the array field to multiple documents.
$group: Group by data.sellerId to sum ($sum) for data.price.
$group: Group by 0 with $addToSet to combine multiple documents into one document with data.
MongoDB aggregation query
db.collection.aggregate([
{
$unwind: "$data"
},
{
$group: {
_id: {
sellerId: "$data.sellerId"
},
"sumPrice": {
$sum: "$data.price"
}
}
},
{
"$group": {
"_id": 0,
"data": {
$addToSet: {
"sellerId": "$_id.sellerId",
"sumPrice": "$sumPrice"
}
}
}
}
])
Sample Mongo Playground
Output
[
{
"_id": 0,
"data": [
{
"sellerId": ObjectId("61307abca667678553be81cb"),
"sumPrice": 4988
}
]
}
]
If you want to re-write the query, here are the query with sample input.
Input
[
{
items: [
{
_id: ObjectId("61435b3c0f773abaf77a367e"),
price: 3000,
type: "service",
sellerId: ObjectId("61307abca667678553be81cb"),
},
{
_id: ObjectId("613115808330be818abaa613"),
price: 788,
type: "product",
sellerId: ObjectId("61307abca667678553be81cb"),
},
{
_id: ObjectId("61307c1ea667676078be81cc"),
price: 1200,
type: "product",
sellerId: ObjectId("61307abca667678553be81cb"),
}
]
}
]
Mongo aggregation query
db.collection.aggregate([
{
$unwind: "$items"
},
{
$group: {
_id: {
sellerId: "$items.sellerId"
},
"sumPrice": {
$sum: "$items.price"
}
}
},
{
"$group": {
"_id": 0,
"data": {
$addToSet: {
"sellerId": "$_id.sellerId",
"sumPrice": "$sumPrice"
}
}
}
}
])
Sample 2 on Mongo Playground
Output
[
{
"_id": 0,
"data": [
{
"sellerId": ObjectId("61307abca667678553be81cb"),
"sumPrice": 4988
}
]
}
]

Convert the string to integer in mongo aggregate query

My company has inserted numerical values for certain keys in string format. They can't be converted to integer format for some business reason.
Now coming to the query...
I am writing a mongo aggregate query which calculates annual cost for a particular manufacturer like Unilever across shops. It seems I cannot convert a string to integer inside the $cond and $eq blocks using $toInt method.
Please find below the sample collection.
[
{
_id: "ddfdfdfdggfgfgsg",
rate: "3323",
quantity_packs: "343",
shop_name: "Whole Foods",
manufacturer_name: "Unilever"
},
{
_id: "ddfdfdfsdsds",
rate: "434",
quantity_packs: "453",
shop_name: "Carrefour",
manufacturer_name: "Unilever"
},
{
_id: "dfdfdgcvgfgfvvv",
rate: "343",
quantity_packs: "23",
shop_name: "Target",
manufacturer_name: "Beirsdorf"
}
]
The query is
db.collection.aggregate([
{
$match: {
manufacturer_name: {
$in: [ "Unilever" ]
}
}
},
{
$group: {
_id: {
"Shop Name": "$shop_name"
},
"annual_cost": {
$sum: {
$cond: [
{
$eq: ["manufacturer_name", "Unilever"]
},
{ "$toInt": "$rate"},
0
]
}
},
"other_annual_cost": {
$sum: {
$cond: [
{
$ne: [$manufacturer_name, "Unilever"]
}, {"$toInt" : "$rate"},
0
]
}
},
"annual_qty": {
$sum: {
"$toInt": "$quantity_packs"
}
},
}
},
{
$project: {
"Purchase_Cost": {
$multiply: [ "$annual_cost", "$annual_qty" ]
},
"Other Manu Pur Cost": {
$multiply: ["$other_annual_cost", "$annual_qty"]
}
}
}
])
Current Output
[
{
_id: { 'Shop Name': 'Whole Foods' },
Purchase_Cost: 0
}
]
As $rate is of string type, the multiplication has yielded 0 as shown over here. Ideally the result should show some integer value for purchase cost as shown below.
Intended Output
[
{
_id: { 'Shop Name': 'Whole Foods' },
Purchase_Cost: 234
}
]
Any suggestion would be of great help. I want to make this query work somehow.
I have updated the question based on Rajdeep's Answer.
I just corrected this, please take a look
Playground
"annual_cost": {
$sum: {
$cond: [
{
$eq: [
"$manufacturer_name", //added $
"Unilever"
]
},
{
$toInt: "$rate" //added $toInt
},
0
]

How to get decimal value after avg calculation in mongodb

I have following 2 collection schema
images:{
imageId:"string", avgRating:{ rating1:decimal,rating2:decimal}, ratingCount:int}
}
ratings:{
imageId:"string", rating1:decimal, rating2:decimal
}
//here rating1 nd rating2 are ratings for different features(just according to my requirements)
so I am calculating avg as follows
db.images.aggregate([
{
$match: {
imageId: "someid",
},
},
{
$lookup:
{
from: "ratings",
let: {id: '$imageId'},
pipeline: [
{
$match: {
{
$eq: ['$imageId','$$id']
},
},
},{
$group:
{
_id: 0,
aggRating1: {$avg: "$rating1"},
aggRating2: {$avg: '$rating2'},
count: {$sum: 1}
}
},
{$project: {_id: 0,count:1,aggRating1:1,aggRating2:1}},
],
as: "rating"
}
},
{
$set: {
ratingCount: '$count',
'avgRating.rating1':'$review.aggRating1'
'avgRating.rating2':'$review.aggRating2'
}
},
]);
I am getting results like this
"data":[
{
"_id": "somedocId",
"imageId":"someid",
"ratingCount": 10,
"avgRating": {
"aggRating1": [
"rating1":{
"$numberDecimal": "3.25"
}],
"aggRating2": [
"rating2":{
"$numberDecimal": "3.25"
}]
},
"rating":[
{
"aggRating1": {
"$numberDecimal": "3.25"
},
"aggRating2": {
"$numberDecimal": "3.25"
},
"count": 10
}
],
}
]
So if u see when I set the aggRating1 and aggRating2 from rating lookup I got, it converts to array. But in rating it is an object. Idk why is that happening.
So how do i get just the decimal value of the avg results? and not like above? :/

Mongoose subquery

I have a collection that looks like below:
[
{
"orderNum": "100",
"createdTime": ISODate("2020-12-01T21:00:00.000Z"),
"amount": 100,
"memo": "100memo",
"list": [
1
]
},
{
"orderNum": "200",
"createdTime": ISODate("2020-12-01T21:01:00.000Z"),
"amount": 200,
"memo": "200memo",
"list": [
1,
2
]
},
{
"orderNum": "300",
"createdTime": ISODate("2020-12-01T21:02:00.000Z"),
"amount": 300,
"memo": "300memo"
},
{
"orderNum": "400",
"createdTime": ISODate("2020-12-01T21:03:00.000Z"),
"amount": 400,
"memo": "400memo"
},
]
and I'm trying to get the total amount of orders that were created before order# 300 (so order#100 and #200, total amount is 300).
Does anyone know how to get it via Mongoose?
You can use this one:
db.collection.aggregate([
{ $sort: { orderNum: 1 } }, // by default the order of documents in a collection is undetermined
{ $group: { _id: null, data: { $push: "$$ROOT" } } }, // put all documents into one document
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } }, // cut desired elementes from array
{ $unwind: "$data" }, // transform back to documents
{ $replaceRoot: { newRoot: "$data" } },
{ $group: { _id: null, total_amount: { $sum: "$amount" } } } // make summary
])
Actually it is not needed to $unwind and $group, so the shortcut would be this:
db.collection.aggregate([
{ $sort: { orderNum: 1 } },
{ $group: { _id: null, data: { $push: "$$ROOT" } } },
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } },
{ $project: { total_amount: { $sum: "$data.amount" } } }
])
But the answer from #turivishal is even better.
Update for additional field
{
$set: {
data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] },
memo: { $arrayElemAt: [ "$data.memo", { $indexOfArray: ["$data.orderNum", "300"] } ] }
}
}
or
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } },
{ $set: { memo: { $last: { "$data.memo" } } },
$match orderNum less than 300
$group by null and get totalAmount using $sum of amount
YourSchemaModel.aggregate([
{ $match: { orderNum: { $lt: "300" } } },
{
$group: {
_id: null,
totalAmount: { $sum: "$amount" }
}
}
])
Playground