Reducing an array of values into object with their count using aggregation framework - mongodb

We are using MongoDB to record statistics. The approach is to record each action for an object in its own document and later aggregate them on hourly basis and store them in different collection. Sample documents are below:
[{
"_id" : ObjectId("5e05de1e86029610dc2c6f9c"),
"object_type" : 1,
"object_id" : 1003,
"browser" : "chrome",
"os" : "osx",
"device" : "android",
"category" : 3,
"country" : "gb",
"action" : "impression",
"date_added" : ISODate("2019-12-26T19:00:00.000Z")
},{
"_id" : ObjectId("5e06226586029610db417b7a"),
"object_type" : 1,
"object_id" : 1003,
"browser" : "firefox",
"os" : "osx",
"device" : "lg_tv",
"category" : 1,
"country" : "pe",
"action" : "impression",
"date_added" : ISODate("2019-12-25T19:00:00.000Z")
},{
"_id" : ObjectId("5e06226586029610db417b7b"),
"object_type" : 1,
"object_id" : 1009,
"browser" : "uc_browser",
"os" : "osx",
"device" : "android",
"category" : 4,
"country" : "ru",
"action" : "view",
"date_added" : ISODate("2019-12-25T19:00:00.000Z")
}]
Output should be:
[{
"object_id": 1003,
"object_type": 1,
"action": "impression",
"total": 2,
"date": "2019-12-26 19:00:00",
"browsers": { "firefox": 1, "chrome": 1 },
"systems": { "osx": 2 },
"countries": { "gb": 1, "pe": 1 },
"devices": { "android": 1, "lg_tv": 1 },
"categories": { "3": 1, "1": 1 }
},
{
"object_id": 1009,
"object_type": 1,
"action": "view",
"total": 1,
"date": "2019-12-26 19:00:00",
"browsers": { "uc_browser": 1 },
"systems": { "osx": 1 },
"countries": { "ru": 1 },
"devices": { "android": 1 },
"categories": { "4": 1 }
}]
Aggregation pipeline:
[
{
"$match": {
"date_added": {
"$gte": {
"$date": {
"$numberLong": "1576820825000"
}
}
}
}
},
{
"$group": {
"_id": {
"object_id": "$object_id",
"object_type": "$object_type",
"action": "$action",
"date": {
"$dateToString": {
"format": "%Y-%m-%d %H-00-00",
"date": "$date_added"
}
}
},
"total": {
"$sum": 1
},
"countries": {
"$push": "$country"
}
}
},
{
"$project": {
"action": "$_id.action",
"object_id": "$_id.object_id",
"object_type": "$_id.object_type",
"date": "$_id.date",
"total": 1,
"countries": 1,
"systems": 1,
"devices": 1,
"categories": 1,
"_id": 0
}
},
{
"$sort": {
"total": -1
}
}
]
This pipeline provides total of an object for a certain action on given hour and push each country into countries array - for readability removed other indexes from $group.
I’m stuck at transforming countries array into desired object. Two question popped in my mind.
Is it possible with single aggregation pipeline?
Should I just return documents using above pipeline and process rest of the indexes with scripting?

It's possible, but a bit tedious...
You need to $group each new field in the next stage and acumulate previous fields.
ASSUMPTION
Your expected result for "object_id": 1003 with total:2, but date_added is 2019-12-26 and 2019-12-25. So, I've changed to 2019-12-26 both documents.
db.collection.aggregate([
{
"$match": {
"date_added": {
"$gte": {
"$date": {
"$numberLong": "1576820825000"
}
}
}
}
},
{
$group: {
_id: {
"object_id": "$object_id",
"object_type": "$object_type",
"action": "$action",
"date": {
"$dateToString": {
"format": "%Y-%m-%d %H-00-00",
"date": "$date_added",
timezone: "GMT"
}
}
},
data: {
"$push": "$$ROOT"
},
total: {
$sum: 1
}
}
},
{
$unwind: "$data"
},
{
$group: {
_id: {
_id: "$_id",
"tmp": "$data.category"
},
data: {
$push: "$data"
},
total: {
$first: "$total"
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: "$_id._id",
data: {
$push: "$data"
},
total: {
$first: "$total"
},
categories: {
$push: {
k: {
$toString: "$_id.tmp"
},
v: "$count"
}
}
}
},
{
$unwind: "$data"
},
{
$unwind: "$data"
},
{
$group: {
_id: {
_id: "$_id",
"tmp": "$data.device"
},
categories: {
$first: "$categories"
},
data: {
$push: "$data"
},
total: {
$first: "$total"
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: "$_id._id",
data: {
$push: "$data"
},
total: {
$first: "$total"
},
categories: {
$first: "$categories"
},
devices: {
$push: {
k: "$_id.tmp",
v: "$count"
}
}
}
},
{
$unwind: "$data"
},
{
$unwind: "$data"
},
{
$group: {
_id: {
_id: "$_id",
"tmp": "$data.country"
},
devices: {
$first: "$devices"
},
categories: {
$first: "$categories"
},
data: {
$push: "$data"
},
total: {
$first: "$total"
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: "$_id._id",
data: {
$push: "$data"
},
total: {
$first: "$total"
},
devices: {
$first: "$devices"
},
categories: {
$first: "$categories"
},
countries: {
$push: {
k: "$_id.tmp",
v: "$count"
}
}
}
},
{
$unwind: "$data"
},
{
$unwind: "$data"
},
{
$group: {
_id: {
_id: "$_id",
"tmp": "$data.os"
},
countries: {
$first: "$countries"
},
devices: {
$first: "$devices"
},
categories: {
$first: "$categories"
},
data: {
$push: "$data"
},
total: {
$first: "$total"
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: "$_id._id",
data: {
$push: "$data"
},
total: {
$first: "$total"
},
countries: {
$first: "$countries"
},
devices: {
$first: "$devices"
},
categories: {
$first: "$categories"
},
systems: {
$push: {
k: "$_id.tmp",
v: "$count"
}
}
}
},
{
$unwind: "$data"
},
{
$unwind: "$data"
},
{
$group: {
_id: {
_id: "$_id",
"tmp": "$data.browser"
},
systems: {
$first: "$systems"
},
countries: {
$first: "$countries"
},
devices: {
$first: "$devices"
},
categories: {
$first: "$categories"
},
data: {
$push: "$data"
},
total: {
$first: "$total"
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: "$_id._id",
data: {
$push: "$data"
},
total: {
$first: "$total"
},
systems: {
$first: "$systems"
},
countries: {
$first: "$countries"
},
devices: {
$first: "$devices"
},
categories: {
$first: "$categories"
},
browsers: {
$push: {
k: "$_id.tmp",
v: "$count"
}
}
}
},
{
$project: {
_id: 0,
action: "$_id.action",
date: "$_id.date",
object_id: "$_id.object_id",
object_type: "$_id.object_type",
total: 1,
categories: {
$arrayToObject: "$categories"
},
countries: {
$arrayToObject: "$countries"
},
devices: {
$arrayToObject: "$devices"
},
systems: {
$arrayToObject: "$systems"
},
browsers: {
$arrayToObject: "$browsers"
}
}
},
{
$sort: {
object_id: 1,
date: 1
}
}
])
MongoPlayground
Note: Other approach was to use $facet and create fields separately and then merge them into final object, but MongoPlayground sometimes worked buggy (click Run button several times and you get different result)

Related

MongoDB lookup - using $lookup

so i have document for users with this structure in JSON format:
[
{
"_id": {
"$oid": "6369aeb83ce0f8168520f42f"
},
"fullname": "Jokona",
"password": "$2b$10$MUAe7XIc/xtJTGVh/y1DeuShCARbwxCSejUbHaqIPZfjekNrn0.Yy",
"NIK": "MT220047",
"status": "active",
"department": "Logistic",
"position": "Management Trainee",
"Group_Shift": "Non Shift",
"role": "admin",
"createdAt": 1667870392,
"updatedAt": 1668564835,
"__v": 0
},
{
"_id": {
"$oid": "6369b17b11e02557349d8de5"
},
"fullname": "Warana",
"password": "$2b$10$0xaqz5V8bar/osWmsCiofet5bY10.ORn8Vme3QC7Dh0HwLHwYOm3a",
"NIK": "17000691",
"status": "active",
"department": "Production",
"position": "Foreman",
"Group_Shift": "R1",
"role": "user",
"__v": 0,
"createdAt": 1667871099,
"updatedAt": 1668496775
},
]
it try to lookitup using mongodb $lookup to get the fullname by joining using the NIK as the foreignnkey,here is what i have try:
const dataAnaylitics = await Answer.aggregate([
// $match stage
{
$group: {
_id: {
username: "$username",
title: "$title",
date: "$date",
},
count: {
$sum: 1,
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
},
},
{
$lookup: {
from: "users",
localField: "username",
foreignField: "NIK",
as: "fullname",
pipeline: [{ $project: { fullname: 0 } }],
},
},
{
$group: {
_id: {
username: "$_id.username",
title: "$_id.title",
},
dates: {
$push: {
k: "$_id.date",
v: "$count",
},
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
},
},
{
$project: {
_id: 0,
username: "$_id.username",
title: "$_id.title",
position: 1,
department: 1,
dates: 1,
},
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$$ROOT",
{
$arrayToObject: "$dates",
},
],
},
},
},
{
$unset: "dates",
},
]);
but the result doesnt returning the fullname field, is there is something wrong with my code? i seek for documentation and already follow the step
In your group stage, since you are grouping based on username, the resulting document will have _id.username as the field. Use this field as localField in your lookup.
{
$lookup: {
from: "users",
localField: "_id.username",
foreignField: "NIK",
as: "fullname",
pipeline: [{ $project: { fullname: 0 } }],
}
i have fix it, hope it will helps other..
const dataAnaylitics = await Answer.aggregate([
// $match stage
{
$group: {
_id: {
username: "$username",
title: "$title",
date: "$date",
},
count: {
$sum: 1,
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
},
},
{
$lookup: {
from: "users",
localField: "_id.username",
foreignField: "NIK",
as: "fullname",
pipeline: [{ $project: { _id: 0, fullname: 1 } }],
},
},
{
$group: {
_id: {
username: "$_id.username",
title: "$_id.title",
},
dates: {
$push: {
k: "$_id.date",
v: "$count",
},
},
position: {
$first: "$position",
},
department: {
$first: "$department",
},
fullname: {
$first: { $arrayElemAt: ["$fullname.fullname", 0] },
},
},
},
{
$project: {
_id: 0,
username: "$_id.username",
title: "$_id.title",
position: 1,
department: 1,
dates: 1,
fullname: 1,
},
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$$ROOT",
{
$arrayToObject: "$dates",
},
],
},
},
},
{
$unset: "dates",
},
]);

How to use $match (multiple conditions) and $group in Mongodb

have list of records with the following fields - postBalance, agentId, createdAt, type. I want to filter by “type” and date. After this is done I want to get the $last postBalance for each agent based on the filter and sum up the postBalance. I have been struggling with this using this query
db.transaction.aggregate(
[{ $match: {
$and: [ {
createdAt: { $gte: ISODate('2022-09-15'), $lt:
('2022-09-16') } },
{ type: "CASH_OUT"}]}},
{
$group:
{
_id: {createdAt: {$last: "$createdAt"}},
totalAmount: { $sum: "$postBalance" },
}
}
]
)
An empty array is returned with this query and there are data in the collection.
Below are samples of the documents
{
"_id": {
"$oid": "6334cefd0048787d5535ff16"
},
"type": "CASH_OUT",
"postBalance": {
"$numberDecimal": "23287.625"
},
"createdAt": {
"$date": {
"$numberLong": "1664405245000"
}
},
}
{
"_id": {
"$oid": "6334d438c1ab8a577677cbf3"
},
"userID": {
"$oid": "62f27bc29f51747015fdb941"
},
"aggregatorID": "0000116",
"transactionFee": {
"$numberDecimal": "0.0"
},
"type": "AIRTIME_VTU",
"postBalance": {
"$numberDecimal": "2114.675"
},
"walletHistoryID": 613266,
"walletID": 1720,
"walletActionAt": {
"$date": {
"$numberLong": "1664406584000"
}
},
{
"type": "FUNDS_TRANSFER",
"postBalance": {
"$numberDecimal": "36566.39"
},
"createdAt": {
"$date": {
"$numberLong": "1664407090000"
}
}
}
This is the output I am expecting
{
"date" : 2022-10-09,
"CASHOUT ": 897663,088,
"FUNDS_TRANSFER": 8900877,
"AIRTIME_VTU": 8890000
}
How can my query be aggregated to get this? Thanks
It look like you want something like:
db.collection.aggregate([
{$match: {
createdAt: {
$gte: ISODate("2022-09-15T00:00:00.000Z"),
$lt: ISODate("2022-09-30T00:00:00.000Z")
}
}
},
{$group: {
_id: "$type",
createdAt: {$first: "$createdAt"},
totalAmount: {$sum: "$postBalance"}
}
},
{$group: {
_id: 0,
createdAt: {$first: "$createdAt"},
data: {$push: {k: "$_id", v: "$totalAmount"}}
}
},
{$project: {
data: {$arrayToObject: "$data"},
createdAt: 1,
_id: 0
}
},
{$set: {"data.date": "$createdAt"}},
{$replaceRoot: {newRoot: "$data"}}
])
See how it works on the playground example

Mongodb join multiple group of values

Hello I'm starting to learn mongoDB query
I have some understanding problems with aggregate
For example, I have these documents:
[
{
totalTaxInclusive: 15,
totalTaxExclusive: 12.5,
payments:[{
method: "CB",
amount: 10
},
{
method: "CASH",
amount: 5
}
],
},
{
totalTaxInclusive: 40,
totalTaxExclusive: 33.33,
payments:[{
method: "CB",
amount: 40
}
],
},
]
and so on.
How can I make a request, who I will have a:
$group: {
_id: "$payments.method",
amount: { $sum: "$payments.amount"},
}
and a
$group: {
_id: null,
totalCount: { $sum: 1 },
totalTaxInclusive: { $sum: "$totalTaxInclusive"},
totalTaxExclusive: { $sum:"$totalTaxExclusive" },
}
To have a result of something like:
{
totalCount: 2,
totalTaxInclusive: 55,
totalTaxExclusive: 45.83,
payments: [{
method: "CASH",
amount: 5,
},
{
method: "CB",
amount: 50,
}
]
}
Thanks a lot for your help.
$group by null and get the sum of required fields, construct the array of payments
$concatArrays to concat arrays
$reduce to concat nested array of payments to array
$unwind deconstruct payments array
$group by method and get the sum of amount and get required fields first value
$group by null and construct the array of payments and get count fields first value
db.collection.aggregate([
{
$group: {
_id: null,
totalTaxInclusive: { $sum: "$totalTaxInclusive" },
totalTaxExclusive: { $sum: "$totalTaxExclusive" },
totalCount: { $sum: 1 },
payments: { $push: "$payments" }
}
},
{
$addFields: {
payments: {
$reduce: {
input: "$payments",
initialValue: [],
in: { $concatArrays: ["$$this", "$$value"] }
}
}
}
},
{ $unwind: "$payments" },
{
$group: {
_id: "$payments.method",
amount: { $sum: "$payments.amount" },
totalTaxInclusive: { $first: "$totalTaxInclusive" },
totalTaxExclusive: { $first: "$totalTaxExclusive" },
totalCount: { $first: "$totalCount" }
}
},
{
$group: {
_id: null,
totalTaxInclusive: { $first: "$totalTaxInclusive" },
totalTaxExclusive: { $first: "$totalTaxExclusive" },
totalCount: { $first: "$totalCount" },
payments: {
$push: {
method: "$_id",
amount: "$amount"
}
}
}
}
])
Playground
First stage to group all documents and store their data in relevant arrays. Also count all documents in this stage.
Second stage to sum all items in totalTaxExclusive and totalTaxInclusive arrays
Third and forth stage to unwind payments because it is an array of arrays
Forth stage to group by payment_method and to get total sum by each payment_method
Fifth stage to output result in requested format
db.collection.aggregate([
{
"$group": {
"_id": null,
"totalTaxInclusive": {
"$addToSet": "$totalTaxInclusive"
},
"totalTaxExclusive": {
"$addToSet": "$totalTaxExclusive"
},
"payments": {
"$addToSet": "$payments"
},
"count": {
"$sum": 1
}
}
},
{
"$set": {
"totalTaxExclusive": {
"$sum": "$totalTaxExclusive"
},
"totalTaxInclusive": {
"$sum": "$totalTaxInclusive"
}
}
},
{
"$unwind": "$payments"
},
{
"$unwind": "$payments"
},
{
"$group": {
"_id": "$payments.method",
"amount": {
"$sum": "$payments.amount"
},
"totalTaxInclusive": {
"$first": "$totalTaxInclusive"
},
"totalTaxExclusive": {
"$first": "$totalTaxExclusive"
},
"count": {
"$first": "$count"
}
}
},
{
"$group": {
"_id": null,
"payments": {
"$addToSet": {
"method": "$_id",
"amount": "$amount"
}
},
"totalTaxInclusive": {
"$first": "$totalTaxInclusive"
},
"totalTaxExclusive": {
"$first": "$totalTaxExclusive"
},
"count": {
"$first": "$count"
}
}
}
])
Working example

Mongodb aggregation group by inner array

I have aggregated my data to give this output.
[
{
"_id": {
"source": "source_1",
"medium": "medium_1",
"campaign": "campaign_1"
},
"visitors": [
{
"_id": "60073f564d6c915237dbe158",
"location": {
"city": "Miami",
"postal": "33177"
}
},
{
"_id": "60073f564d6c915237dbe158",
"location": {
"city": "Miami",
"postal": "33163"
}
}
]
},
{
"_id": {
"source": "source_2",
"medium": "medium_2",
"campaign": "campaign_2"
},
"visitors": [
{
"_id": "60073f564d6c915237dbe158",
"location": {
"city": "Miami",
"postal": "33177"
}
},
{
"_id": "60073f564d6c915237dbe158",
"location": {
"city": "Miami",
"postal": "33162"
}
}
]
}
]
I want to group inner visitors array and get this output.
[
{
"_id": {
"source": "source_1",
"medium": "medium_1",
"campaign": "campaign_1"
},
"visitors": [
{
"city": "Miami",
"postal": "33177",
"count": 2
},
{
"city": "Miami",
"postal": "33163",
"count": 5
}
]
},
{
"_id": {
"source": "source_2",
"medium": "medium_2",
"campaign": "campaign_2"
},
"visitors": [
{
"city": "Miami",
"postal": "33177",
"count": 1
},
{
"city": "Miami",
"postal": "33163",
"count": 3
}
]
}
]
aggregate pipeline executed on campaigns collection:
[{$match: {
website_id: 1,
$or: [
{
source:{
$regex:/goo/,
$options: 'i'
}
},
{
medium:{
$regex:/goo/,
$options: 'i'
}
},
{
campaign:{
$regex:/goo/,
$options: 'i'
}
}
]
}}, {$addFields: {
visitor_id: {
$toObjectId: "$visitor_id"
}
}}, {$lookup: {
from: 'visitors',
localField: 'visitor_id',
foreignField: '_id',
as: 'visitors'
}}, {$unwind: {
path: '$visitors'
}}, {$group: {
_id: {
source: '$source',
medium: '$medium',
campaign: '$campaign',
},
visitors:{
$push: '$visitors'
}
}}, {$unwind: {
path: '$visitors'
}}, {$group: {
_id: {
'city': '$visitors.location.city',
'postal': '$visitors.location.postal'
},
'count': {
'$sum': 1
}
}}, {$project: {
'_id': 0,
'city': '$_id.city',
'postal': '$_id.postal',
'count': '$count',
'total': {
'$sum': '$count'
}
}}, {$project: {
'city': '$city',
'postal': '$postal',
'count': '$count',
'total': {
'$sum': '$total'
}
}}]
So the idea is first group the visitors by their postal number along with the campaign details to get the count and then aggregate it by only campaign details to accumulate the visitors.
Try this query:
db.campaigns.aggregate([
{
$match: {
// Put your condtions here.
}
},
{
$project: {
source: 1,
medium: 1,
campaign: 1,
visitor_id: 1
}
},
{
$addFields: {
visitor_id: { $toObjectId: "$visitor_id" }
}
},
{
$lookup: {
from: "visitors",
let: { "visitor_id": "$visitor_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$_id", "$$visitor_id"] }
}
},
{
$project: {
location: {
city: 1,
postal: 1
}
}
}
],
as: "visitor"
}
},
{ $unwind: "$visitor" },
{
$group: {
_id: {
source: "$source",
medium: "$medium",
campaign: "$campaign",
postal: "$visitor.location.postal"
},
visitors: { $push: "$visitor" },
count: { $sum: 1 }
}
},
{
$group: {
_id: {
source: "$_id.source",
medium: "$_id.medium",
campaign: "$_id.campaign"
},
visitors: {
$push: {
city: { $arrayElemAt: ["$visitors.location.city", 0] },
postal: { $arrayElemAt: ["$visitors.location.postal", 0] },
count: "$count"
}
}
}
}
]);
You need to correct group stage,
$group by source, medium, campaign and postal, get first city and count total sum
$group by source, medium, campaign and construct visitors array with required fields
db.campaigns.aggregate([
{ $match: .. } //skipped
{ $addFields: .. }, //skipped
{ $lookup: .. }, //skipped
{ $unwind: .. }, //skipped
{
$group: {
_id: {
source: "$source",
medium: "$medium",
campaign: "$campaign",
postal: "$visitors.location.postal"
},
city: { $first: "$visitors.location.city" },
count: { $sum: 1 }
}
},
{
$group: {
_id: {
source: "$_id.source",
medium: "$_id.medium",
campaign: "$_id.campaign"
},
visitors: {
$push: {
city: "$city",
postal: "$_id.postal",
count: "$count"
}
}
}
}
])
Playground

Group on field while getting the last document for each field with MongoDB

Problem
I'm trying to group a stock inventory by products. At first, my stock entries was fully filled each time so I made this aggregate:
[
{ $sort: { date: 1 } },
{
$group: {
_id: '$userId',
stocks: { $last: '$stocks' },
},
},
{ $unwind: '$stocks' },
{
$group: {
_id: '$stocks.productId',
totalQuantity: { $sum: '$stocks.quantity' },
stocks: { $push: { userId: '$_id', quantity: '$stocks.quantity' } },
},
},
]
Now, it can be possible that a stock entry doesn't contain all the products filled. So I'm stuck while writing the new aggregate.
Basically I need to group every products by productId and have an array of the last entry for each user.
Output
This is my expected output:
[
{
"_id": ObjectId("5e75eae1359fc8159d5b6073"),
"totalQuantity": 33,
"stocks": [
{
"userId": ObjectId("5e75f498359fc8159d5b6075"),
"lastDate": "2020-03-21T11:45:53.077Z",
"quantity": 33
}
]
},
{
"_id": ObjectId("5e75eaea359fc8159d5b6074"),
"totalQuantity": 2,
"stocks": [
{
"userId": ObjectId("5e75f498359fc8159d5b6075"),
"lastDate": "2020-03-21T11:45:53.077Z",
"quantity": 2
}
]
}
]
Documents
Documents (when fully filled):
{
"_id": ObjectId("5e75fe71e4a3e0323ba47e0a"),
"date": "2020-03-21T11:45:53.077Z",
"userId": ObjectId("5e75f498359fc8159d5b6075"),
"stocks": [
{
"productId": ObjectId("5e75eae1359fc8159d5b6073"),
"quantity": 33
},
{
"productId": ObjectId("5e75eaea359fc8159d5b6074"),
"quantity": 2
}
]
}
Sometimes it won't be filled for the whole inventory (that's why I need the lastDate):
{
"_id": ObjectId("5e75fe71e4a3e0323ba47e0a"),
"date": "2020-03-21T11:45:53.077Z",
"userId": ObjectId("5e75f498359fc8159d5b6075"),
"stocks": [
{
"productId": ObjectId("5e75eae1359fc8159d5b6073"),
"quantity": 33
}
]
}
Try this one:
db.collection.aggregate([
{
$group: {
_id: "$userId",
root: {
$push: "$$ROOT"
}
}
},
{
$addFields: {
root: {
$map: {
input: "$root",
as: "data",
in: {
"stocks": {
$map: {
input: "$$data.stocks",
as: "stock",
in: {
"productId": "$$stock.productId",
"userId": "$$data.userId",
"quantity": "$$stock.quantity",
"lastDate": "$$data.date"
}
}
}
}
}
}
}
},
{
$unwind: "$root"
},
{
$replaceRoot: {
newRoot: "$root"
}
},
{
$unwind: "$stocks"
},
{
$sort: {
"stocks.lastDate": 1
}
},
{
$group: {
_id: "$stocks.productId",
totalQuantity: {
$last: "$stocks.quantity"
},
stocks: {
$last: "$stocks"
}
}
},
{
$addFields: {
stocks: [
{
"lastDate": "$stocks.lastDate",
"quantity": "$stocks.quantity",
"userId": "$stocks.userId"
}
]
}
}
])
MongoPlayground