Mongodb Aggregation :- Get specific fields from $lookup nested array - mongodb

I am trying to get specific fields from the array I got after aggregate, lookup and some cond
Below you can see my query
const attendanceData = await User.aggregate([
{
$match: {
lastLocationId: Mongoose.Types.ObjectId(typeId),
isActive: true,
},
},
{
$project: {
_id: 1,
workerId: 1,
workerFirstName: 1,
workerSurname: 1,
},
},
{
$lookup: {
from: "attendances",
localField: "_id",
foreignField: "employeeId",
as: "attendances",
},
},
{
$set: {
attendances: {
$filter: {
input: "$attendances",
cond: {
$and: [
{
$gte: ["$$this.Date", new Date(fromDate)],
},
{
$lte: ["$$this.Date", new Date(toDate)],
},
{
$eq: ["$$this.createdAs", dataType],
},
{
$eq: ["$$this.status", true],
},
{
$eq: ["$$this.workerType", workerType],
},
],
},
},
},
},
},
{ $skip: 0 },
{ $limit: 10 },
]);
The data as a response i get below
{
"attendanceSheet": [
{
"_id": "60dd77c14524e6c116e16aaa",
"workerFirstName": "FIRST NAME1",
"workerSurname": "SURNAME1",
"workerId": "1",
"attendances": [
{
"_id": "6130781085b5055a15c32f2u",
"workerId": "1",
"workerFullName": "FIRST NAME",
"workerType": "Employee",
"Date": "2022-10-01T00:00:00.000Z",
"createdAs": "ABSENT"
},
{
"_id": "6130781085b5055a15c32f2u",
"workerId": "1",
"workerFullName": "FIRST NAME",
"workerType": "Employee",
"Date": "2022-10-02T00:00:00.000Z",
"createdAs": "ABSENT"
}
]
},
{
"_id": "60dd77c24524e6c116e16c0f",
"workerFirstName": "FIRST NAME2",
"workerSurname": "Surname",
"workerId": "2",
"attendances": [
{
"_id": "6130781a85b5055a15c3455y",
"workerId": "2",
"workerFullName": "FIRST NAME2",
"workerType": "Employee",
"Date": "2022-10-02T00:00:00.000Z",
"createdAs": "ABSENT"
}
]
}
]
}
But I want data something like this below only few fields in not every fields
{
"attendanceSheet": [
{
"_id": "60dd77c14524e6c116e16aaa",
"workerFirstName": "FIRST NAME1",
"workerSurname": "Surname",
"workerId": "1",
"attendances": [
{
"_id": "6130781085b5055a15c32f2u",
"Date": "2022-10-01T00:00:00.000Z",
"createdAs": "ABSENT"
},
{
"_id": "6130781085b5055a15c32f2u",
"Date": "2022-10-02T00:00:00.000Z",
"createdAs": "ABSENT"
}
]
},
{
"_id": "60dd77c24524e6c116e16c0f",
"workerFirstName": "FIRST NAME2",
"workerSurname": "Surname",
"workerId": "2",
"attendances": [
{
"_id": "6130781a85b5055a15c3455y",
"Date": "2022-10-02T00:00:00.000Z",
"createdAs": "ABSENT"
}
]
}
]
}

You could simplify/refactor your aggregation pipeline by putting all the matching in a "$lookup" "pipeline".
db.users.aggregate([
{
"$match": {
"lastLocationId": ObjectId("0123456789abcdef01234567"),
"isActive": true
}
},
{
"$project": {
"workerId": 1,
"workerFirstName": 1,
"workerSurname": 1
}
},
{
"$lookup": {
"from": "attendances",
"localField": "_id",
"foreignField": "employeeId",
"as": "attendances",
// do all the matching here
"pipeline": [
{
"$match": {
"Date": {
// fromDate, toDate
"$gte": ISODate("2022-09-01T00:00:00Z"),
"$lte": ISODate("2022-09-30T23:59:59Z")
},
// dataType
"createdAs": "ABSENT",
"status": true,
// workerType
"workerType": "Employee"
}
},
{
"$project": {
"Date": 1,
"createdAs": 1
}
}
]
}
},
{$skip: 0},
{$limit: 10}
])
Try it on mongoplayground.net.

One option to get from what you have to the requested output is to $map and $reduce:
db.collection.aggregate([
{
$set: {
attendanceSheet: {
$map: {
input: "$attendanceSheet",
as: "external",
in: {
$mergeObjects: [
"$$external",
{
attendances: {
$reduce: {
input: "$$external.attendances",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{
_id: "$$this._id",
createdAs: "$$this.createdAs",
Date: "$$this.Date"
}
]
]
}
}
}
}
]
}
}
}
}
}
])
See how it works on the playground example

The below modification worked for me
const attendanceData = await User.aggregate([
{
$match: {
lastLocationId: Mongoose.Types.ObjectId(typeId),
isActive: true,
},
},
{
$project: {
_id: 1,
workerId: 1,
workerFirstName: 1,
workerSurname: 1,
},
},
{
$lookup: {
from: "attendances",
localField: "_id",
foreignField: "employeeId",
as: "attendances",
},
},
{
$set: {
attendances: {
$filter: {
input: "$attendances",
cond: {
$and: [
{
$gte: ["$$this.Date", new Date(fromDate)],
},
{
$lte: ["$$this.Date", new Date(toDate)],
},
{
$eq: ["$$this.createdAs", dataType],
},
{
$eq: ["$$this.status", true],
},
{
$eq: ["$$this.workerType", workerType],
},
],
},
},
},
},
},
{
$set: {
attendances: {
$reduce: {
input: "$attendances",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{
_id: "$$this._id",
createdAs: "$$this.createdAs",
Date: "$$this.Date",
},
],
],
},
},
},
},
},
{ $skip: 0 },
{ $limit: 10 },
]);

Related

Mongodb Aggregate Filter Array Of Array Of Array

We would like to filter SKU's List which has verificationData data and differenceInStock difference greater than or Less than 0
Here is an example Data Set.
[
{
"_id": "636e0beaa13ef73324e613f0",
"status": "ACTIVE",
"inventory": 132,
"parentCategory": [
"Salt"
],
"title": "Aashirvaad MRP: 28Rs Salt 27 kg Bopp Bag (Set of 1 kg x 27)",
"createdAt": "2022-11-11T08:46:34.950Z",
"updatedAt": "2022-11-24T17:43:27.361Z",
"__v": 3,
"verificationData": [
{
"_id": "637c57ebbe783a9a138fc2d3",
"verificationDate": "2022-11-22T05:02:35.155Z",
"items": {
"listingId": "636e0beaa13ef73324e613f0",
"phyiscalVerification": [
{
"verifiedBy": "634534e72ef6462fcb681a39",
"closingStock": 178,
"phyiscalStock": 178,
"differenceInStock": 0,
"verifiedAt": "2022-11-22T10:19:38.388Z",
"_id": "637ca23abe783a9a1394f402"
}
],
"_id": "637ca23abe783a9a1394f401"
},
"yearMonthDayUTC": "2022-11-22"
},
{
"_id": "637d9b65be783a9a13998726",
"verificationDate": "2022-11-23T04:02:45.804Z",
"items": {
"listingId": "636e0beaa13ef73324e613f0",
"phyiscalVerification": [
{
"verifiedBy": "634534e72ef6462fcb681a39",
"closingStock": 161,
"phyiscalStock": 167,
"differenceInStock": 6,
"verifiedAt": "2022-11-23T09:52:36.815Z",
"_id": "637ded64be783a9a13a29d55"
}
],
"_id": "637ded64be783a9a13a29d54"
},
"yearMonthDayUTC": "2022-11-23"
},
{
"_id": "637f0254be783a9a13a94354",
"verificationDate": "2022-11-24T05:34:12.995Z",
"items": {
"listingId": "636e0beaa13ef73324e613f0",
"phyiscalVerification": [
{
"verifiedBy": "634534e72ef6462fcb681a39",
"closingStock": 144,
"phyiscalStock": 146,
"differenceInStock": 2,
"verifiedAt": "2022-11-24T12:02:28.123Z",
"_id": "637f5d54be783a9a13b1039a"
}
],
"_id": "637f5d54be783a9a13b10399"
},
"yearMonthDayUTC": "2022-11-24"
},
{
"_id": "2022-11-25",
"yearMonthDayUTC": "2022-11-25",
"items": null
}
]
},
{
"_id": "62b5c39062ddb963fc64c42d",
"status": "ACTIVE",
"inventory": 10,
"parentCategory": [
"Salt"
],
"finalMeasurementUnit": "kg",
"finalMeasure": "1 kg",
"title": "Marvella Citric Acid Lemon Salt 1 kg Pouch (Set of 500 gm x 2)",
"createdAt": "2022-06-24T14:00:49.052Z",
"updatedAt": "2022-11-21T11:04:21.643Z",
"__v": 2,
"verificationData": [
{
"_id": "2022-11-22",
"yearMonthDayUTC": "2022-11-22",
"items": null
},
{
"_id": "2022-11-23",
"yearMonthDayUTC": "2022-11-23",
"items": null
},
{
"_id": "2022-11-24",
"yearMonthDayUTC": "2022-11-24",
"items": null
},
{
"_id": "2022-11-25",
"yearMonthDayUTC": "2022-11-25",
"items": null
}
]
}
]
This could have array of 100+ SKU's
Our Aggregate Functions is as Follows
let reqData = await userListing.aggregate([
{
$match: {
warehouseId: { $eq: ObjectId(warehouseId) },
parentCategory: { $in: catList },
isWarehouseListing: { $eq: true },
isBlocked: { $ne: true },
isArchived: { $ne: true },
},
},
{ $sort: { whAddedAt: -1 } },
{
$lookup: {
from: "listingstockverifications",
let: { listId: "$_id" },
pipeline: [
{
$match: {
verificationDate: {
$gte: newFromDate,
$lt: newToDate,
},
},
},
{
$project: {
verificationDate: 1,
items: {
$filter: {
input: "$items",
cond: {
$and: [
/* {
"$$this.phyiscalVerification": {
$filter: {
input: "$$this.phyiscalVerification",
as: "psitem",
cond: { $gt: [ "$$psitem.differenceInStock", 0 ] },
},
},
}, */
{
$eq: ["$$this.listingId", "$$listId"],
},
],
},
},
},
yearMonthDayUTC: {
$dateToString: {
format: "%Y-%m-%d",
date: "$verificationDate",
},
},
},
},
{ $unwind: "$items" },
],
as: "stockVerification",
},
},
{
$addFields: {
verificationData: {
$map: {
input: dummyArray,
as: "date",
in: {
$let: {
vars: {
dateIndex: {
$indexOfArray: [
"$stockVerification.yearMonthDayUTC",
"$$date",
],
},
},
in: {
$cond: {
if: { $ne: ["$$dateIndex", -1] },
then: {
$arrayElemAt: ["$stockVerification", "$$dateIndex"],
},
else: {
_id: "$$date",
yearMonthDayUTC: "$$date",
items: null,
},
},
},
},
},
},
},
},
},
{
$project: {
stockVerification: 0,
},
},
]);
At Last now we would like to filter the SKU List the which has following Data
verificationData[].items.phyiscalVerification[].differenceInStock is Greater than or Less than 0
Expected Output in the following Exmaple would be 1st SKUs
as 2nd SKU does not have any Item Data
and even if in 3rd SKU if we got Item Data but should match the following condition
verificationData[].items.phyiscalVerification[].differenceInStock is Greater than or Less than 0
Thank you for taking your time to read and support.
You can add these two following stages to your aggregation, The idea is simple - just filter out all subdocuments that do not match the condition.
Because of the nested structure it's just not the sexiest of pipelines but it will suffice.
db.collection.aggregate([
{
$match: {
$or: [
{
"verificationData.items.phyiscalVerification.differenceInStock": {
$gt: 0
}
},
{
"verificationData.items.phyiscalVerification.differenceInStock": {
$lt: 0
}
}
]
}
},
{
$addFields: {
verificationData: {
$filter: {
input: {
$map: {
input: {
$filter: {
input: "$verificationData",
as: "verification",
cond: {
$ne: [
"$$verification.items",
null
]
}
}
},
as: "top",
in: {
$mergeObjects: [
"$$top",
{
"items": {
"$mergeObjects": [
"$$top.items",
{
phyiscalVerification: {
$filter: {
input: "$$top.items.phyiscalVerification",
as: "pshycical",
cond: {
$ne: [
"$$pshycical.differenceInStock",
0
]
}
}
}
}
]
}
}
]
}
}
},
cond: {
$gt: [
{
$size: "$$this.items.phyiscalVerification"
},
0
]
}
}
}
}
}
])
Mongo Playground

Get min value from array of object using aggregate and lookup mongodb

I have two collections properties and property_prices and the relation is one property many prices. So I am trying to join them and then find min value from property_prices.monthly_unit_price.unit_price. So I could get the Properties with their prices and min unit_price value from entire property pricing.
Property Collection
{
"_id": "1",
"status": "Approved",
"name": "My Property Lake"
}
Property Price Collection where monthly_unit_price have objects from Jan - Dec
{
"property_prices": [
{
"property_id": "1",
"block_id": "ABC",
"monthly_unit_price": [{ "month": "Jan", "unit_price": 100 }, { "month": "Dec", "unit_price": "1200" }],
},
{
"property_id": "1",
"block_id": "DEF",
"monthly_unit_price": [{ "month": "Jan", "unit_price": "200" }, { "month": "Dec", "unit_price": "2400" }],
}
]
}
Basically I want to get the min value from property_prices unit_price for property_id 1
So I tried using aggregate and lookup but I cant get the min value for entire property from property_prices.
Here is what I tried
await Property.aggregate([
{
$lookup: {
from: 'property_prices',
as: 'property_prices',
let: { property_id: '$_id' },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$property_id', '$$property_id'] },
{ $eq: ['$status', 'Completed'] },
]
}
}
},
]
},
},
{
$unwind: "$property_prices"
},
{
$group: {
_id: '$property_prices.property_id',
minInvestment: { "$min": "$property_prices.monthly_unit_price.unit_price" }
}
},
]);
Result I am expecting is
{
"_id": "1",
"status": "Approved",
"name": "My Property Lake",
"property_prices": [
{
"property_id": "1",
"block_id": "ABC",
"monthly_unit_price": [{ "month": "Jan", "unit_price": 100 }, { "month": "Dec", "unit_price": "1200" }],
},
{
"property_id": "1",
"block_id": "DEF",
"monthly_unit_price": [{ "month": "Jan", "unit_price": "200" }, { "month": "Dec", "unit_price": "2400" }],
}
],
"minInvestment":100
}
You are on the right track, you just need to "massage" the document structure a little bit more due to the fact it's a nested array. here is a quick example of doing so using the $map and $reduce operators.
Notice I also had to cast the values to number type using $toInt, I recommend these sort of things to be handled at update/insertion time instead.
db.properties.aggregate([
{
$lookup: {
from: "property_prices",
as: "property_prices",
let: {
property_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$property_id",
"$$property_id"
]
},
{
$eq: [
"$status",
"Completed"
]
}
]
}
}
}
]
}
},
{
$addFields: {
minInvestment: {
$min: {
$reduce: {
input: {
$map: {
input: "$property_prices",
as: "property",
in: {
$map: {
input: "$$property.monthly_unit_price",
as: "price",
in: {
$toInt: "$$price.unit_price"
}
}
}
}
},
initialValue: [],
in: {
"$concatArrays": [
"$$value",
"$$this"
]
}
}
}
}
}
}
])
Mongo Playground

Group documents from multiple collections by date

I have 3 schema with a basic structure
meal: {
user: 'objectID',
createdAt: 'date
}
activity: {
user: 'objectID',
createdAt: 'date'
}
role: {
user: 'objectID',
createdAt: 'date'
}
I would like to get all documents from each schema belonging to a user and group them by dates. For example, a response of
history: [
{
date: 01-11-2021,
meal: [
...array of meal documents on 01-11-2021
],
activity: [
...array of meal documents on 01-11-2021
],
role: [
...array of meal documents on 01-11-2021
],
},
...next date
]
data
db={
"user": [
{
"_id": 1,
"name": "Sam"
}
],
"meal": [
{
"user": 1,
"content": "apple",
"createdAt": ISODate("2021-09-01T11:23:25.184Z")
},
{
"user": 1,
"content": "orange",
"createdAt": ISODate("2021-09-01T11:23:25.184Z")
},
{
"user": 1,
"content": "pie",
"createdAt": ISODate("2021-09-02T11:23:25.184Z")
}
],
"activity": [
{
"user": 1,
"content": "baseball",
"createdAt": ISODate("2021-09-01T11:23:25.184Z")
}
],
"role": [
{
"user": 1,
"content": "admin",
"createdAt": ISODate("2021-09-01T11:23:25.184Z")
}
]
}
aggreagte
db.user.aggregate([
{
"$match": {
_id: 1
}
},
{
"$lookup": {
"from": "meal",
"localField": "_id",
"foreignField": "user",
"pipeline": [
{
"$set": {
"from": "meal"
}
}
],
"as": "meal_docs"
}
},
{
"$lookup": {
"from": "activity",
"localField": "_id",
"foreignField": "user",
"pipeline": [
{
"$set": {
"from": "activity"
}
}
],
"as": "activity_docs"
}
},
{
"$lookup": {
"from": "role",
"localField": "_id",
"foreignField": "user",
"pipeline": [
{
"$set": {
"from": "role"
}
}
],
"as": "role_docs"
}
},
{
$project: {
user: "$name",
items: {
$concatArrays: [
"$activity_docs",
"$meal_docs",
"$role_docs"
]
}
}
},
{
"$unwind": "$items"
},
{
$project: {
createdAt: {
$dateTrunc: {
"date": "$items.createdAt",
"unit": "day"
}
},
content: "$items.content",
from: "$items.from"
}
},
{
"$group": {
"_id": {
"createdAt": "$createdAt",
"from": "$from"
},
"list": {
"$push": "$$ROOT.content"
}
}
},
{
"$group": {
"_id": "$_id.createdAt",
"documents": {
"$push": {
k: "$$ROOT._id.from",
v: "$$ROOT.list"
}
}
}
},
{
"$project": {
documents: {
$arrayToObject: "$documents"
}
}
},
{
"$group": {
"_id": 1,
"history": {
"$push": {
date: "$$ROOT._id",
activity: "$$ROOT.documents.activity",
meal: "$$ROOT.documents.meal",
role: "$$ROOT.documents.role"
}
}
}
}
])
mongoplayground

How to use `$unwind` to iterate exact value

I am new to mongoose and mongodb.
In my addtocart schema I have added $lookups and projection to populate products in add to cart.
In current response product_purchase_quantity it was in array in add to cart collection it is key and value. So, in $lookups I tried to add {$unwind: '$product_purchase_quantity'} but after adding add_to_cart_products object prints twice. I don't know where the mistake was?
Below i have mentioned expected result.
AddToCart Schema:
lookups: [
{
from: 'shop_db_products',
let: {
productId: '$add_to_cart_products.product',
purchaseQuantity: '$add_to_cart_products.product_quantity',
productItemId: '$add_to_cart_products.product_item',
},
pipeline: [
{
$match: { $expr: { $in: ['$_id', '$$productId'] } },
},
{
$lookup: {
from: 'shop_db_products',
localField: 'product_id',
foreignField: '_id',
as: 'products',
},
},
{
$project: {
_id: true,
product: {
_id: '$_id',
product_name: '$product_name',
},
product_purchase_quantity: '$$purchaseQuantity',
product_item: {
$reduce: {
input: {
$filter: {
input: '$product_items',
cond: {
$in: ['$$this._id', '$$productItemId'],
},
},
},
initialValue: {},
in: {
_id: '$$this._id',
product_size: { $concat: [{ $toString: '$$this.product_size.value' }, '$$this.product_size.unit'] },
product_price: '$$this.product_price',
product_type: '$$this.product_type'
},
},
},
},
},
],
localField: '',
as: 'add_to_cart_products',
model: 'ProductModel',
},
],
AddToCart Collection
[
{
"add_to_cart_user": "5f0076b7bd530928fc0c0285",
"add_to_cart_products": [
{
"product": "5f05a0270b4f3a5c41c70826",
"product_item": "5f05a0270b4f3a5c41c70877",
"product_quantity": 5
},
{
"product": "5f05a0270b4f3a5c41c70827",
"product_item": "5f05a0270b4f3a5c41c70666",
"product_quantity": 3
}
],
"add_to_cart_product_total": 5,
"add_to_cart_discount": 50,
"add_to_tax": "5eae321d21924800122f978e",
"add_to_cart_grand_total": 500
}
]
Current Response:
{
"_id": "5fa2a09b3c6316482098f6ff",
"add_to_cart_status_is_active": true,
"add_to_cart_discount": 50,
"add_to_cart_tax": 8,
"add_to_cart_products": [
{
"product": {
"_id": "5f05a0270b4f3a5c41c70826",
"product_name": "Avery Apricot Sour"
},
"product_purchase_quantity": [
5,
3
],
"product_item": {
"_id": "5f05a0270b4f3a5c41c70877",
"product_size": "22oz",
"product_price": 13.99
}
},
{
"product": {
"_id": "5f05a0270b4f3a5c41c70827",
"product_name": "Avery Dugana"
},
"product_purchase_quantity": [
5,
3
],
"product_item": {
"_id": "5f05a0270b4f3a5c41c70666",
"product_size": "22oz",
"product_price": 8.99
}
}
]
}
Expected response:
{
"_id": "5fa2a09b3c6316482098f6ff",
"add_to_cart_status_is_active": true,
"add_to_cart_discount": 50,
"add_to_cart_tax": 8,
"add_to_cart_products": [
{
"product": {
"_id": "5f05a0270b4f3a5c41c70826",
"product_name": "Avery Apricot Sour"
},
"product_purchase_quantity": 5,
"product_item": {
"_id": "5f05a0270b4f3a5c41c70877",
"product_size": "22oz",
"product_price": 13.99
}
},
{
"product": {
"_id": "5f05a0270b4f3a5c41c70827",
"product_name": "Avery Dugana"
},
"product_purchase_quantity": 3,
"product_item": {
"_id": "5f05a0270b4f3a5c41c70666",
"product_size": "22oz",
"product_price": 8.99
}
}
]
}
Product (shop_db_products) collection:
[
{
"_id": "5f05a0270b4f3a5c41c70826",
"product_no": "PRO04087",
"product_store_no": "1001",
"product_dept_no": "Irish Whiskey",
"product_name": "2Gingers",
"product_overview": "No Overview",
"product_items": [
{
"_id": "5f05a0270b4f3a5c41c70877",
"product_item_number": "857566003019",
"product_price": 20.99,
"product_cost": 20.99,
"product_size": "750ml",
"product_type": "Bottle",
"product_value": 0,
"product_quantity": 0,
"product_images": [
"pro04087-857566003019-1.png"
],
"product_item_is_active": true
}
]
},
...
]
You have to $unwind add_to_cart_products before performing $lookup
db.addToCart.aggregate([
{
$unwind: "$add_to_cart_products"
},
{
$lookup: {
from: "shop_db_products",
let: {
productId: "$add_to_cart_products.product",
purchaseQuantity: "$add_to_cart_products.product_quantity",
productItemId: "$add_to_cart_products.product_item"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$_id",
"$$productId"
]
}
}
},
{
$project: {
_id: true,
product: {
_id: "$_id",
product_name: "$product_name",
},
product_purchase_quantity: "$$purchaseQuantity",
product_item: {
$reduce: {
input: {
$filter: {
input: "$product_items",
cond: {
$eq: [
"$$this._id",
"$$productItemId"
],
},
},
},
initialValue: {},
in: {
_id: "$$this._id",
product_size: "$$this.product_size",
product_price: "$$this.product_price",
product_type: "$$this.product_type"
}
}
}
}
}
],
as: "add_to_cart_products"
}
},
{
$unwind: "$add_to_cart_products"
},
{
$group: {
_id: "$_id",
"add_to_cart_products": {
$push: "$add_to_cart_products"
}
}
}
])
MongoDB Playground
Another way
Without reducer, with $unwind
https://mongoplayground.net/p/3uWA5pVBv83
db.addToCart.aggregate([
{
"$unwind": "$add_to_cart_products"
},
{
"$lookup": {
"from": "shop_db_products",
"let": {
"productId": "$add_to_cart_products.product",
"purchaseQuantity": "$add_to_cart_products.product_quantity",
"productItemId": "$add_to_cart_products.product_item"
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": [
"$_id",
"$$productId"
]
}
}
},
{
"$unwind": "$product_items"
},
{
"$match": {
"$expr": {
"$eq": [
"$product_items._id",
"$$productItemId"
]
}
}
},
{
"$project": {
"_id": true,
"product": {
"_id": "$_id",
"product_name": "$product_name"
},
"product_purchase_quantity": "$$purchaseQuantity",
"product_item": "$product_items"
}
}
],
"as": "productResolved"
}
},
{
"$unwind": "$productResolved"
},
{
"$group": {
"_id": "$_id",
"add_to_cart_products": {
"$push": "$productResolved"
}
}
}
])

Match and add new field to each array elements in Mongodb aggregation

I have two collections below and I'm creating a view table by using the aggregation and merge.
account table:
{
"_id": {
"$oid": "5f61b573db16185cebfeaaf9"
},
"status": "ACTIVE",
"personalInfo": {
"entityName": "John Doe",
...
},
"createdDateTime": {
"$date": "2020-09-16T06:49:23.103Z"
},
"modifiedDateTime": {
"$date": "2020-09-16T06:49:23.103Z"
}
}
account_transactions:
{
"_id": {
"$oid": "5f61b5b3db16185cebfeab0c"
},
"accountId": "5f61b573db16185cebfeaaf9",
"transactionType": "Payment",
"transactionStatus": "NEW",
"createdDate": {
"$date": "2020-09-16T06:50:27.305Z"
},
"createdBy": "sujal.shakya#joinsage.com",
"lastModifiedDate": {
"$date": "2020-09-16T10:23:06.617Z"
}
}
{
"_id": {
"$oid": "5f61b5b3db16185cebfeab0f"
},
"accountId": "5f61b573db16185cebfeaaf9",
"transactionType": "Credit",
"createdDate": {
"$date": "2020-09-16T06:50:27.305Z"
},
"createdBy": "sujal.shakya#joinsage.com",
"lastModifiedDate": {
"$date": "2020-09-16T10:23:06.617Z"
}
}
Below mongodb query creates a view table with each account having multiple transactions if exist and if no transactions exists then adds dummy transaction to with some dummy fields.
db.account.aggregate(
[
{
$match: {
'status': 'ACTIVE'
}
},
{$addFields: {"accountId": {"$toString": "$_id"}}},
{
$lookup: {
from: "account_transactions",
localField: "accountId",
foreignField: "accountId",
as: "transactions"
}
},
{
"$project": {
"accountType": 1,
"transactions": {
"$cond": [
{ "$eq": [ "$transactions", [] ] },
{$concatArrays: [[{"lastModifiedDate": {$arrayElemAt: ["$account.modifiedDateTime",0]}, "transactionType" : "BLANK"}]] },//adds dummy fields
{"$filter": {
"input": "$transactions",
"as": "transactions",
"cond": {
"$or": [
{
"$eq": [
"$$transactions.transactionType",
"Payment"
]
},
{
"$eq": [
"$$transactions.transactionType",
"Sale"
]
},
{
"$eq": [
"$$transactions.transactionType",
"Debit"
]
}
]
}
}
}
},
"createdBy": 1
}
},
{$merge: {into: "account_view_table", on: "_id", whenMatched: "replace", whenNotMatched: "insert"}}
])
What I need to do now is to add a new field in the view table is created above for each transactions by matching transaction type. For example: if an account has multiple transactions each with different transaction type, then check the transactionType of each transactions and add new fields "transactionTypeOrder" on each like:
I've been trying different things but nothing is working
{$addFields: {"transactions.transactionTypeOrder" :
{
$cond: [{$in:["$transactions.transactionType",["Payment"]]},"1",
{$cond: [ {$in:["$transactions.transactionType",["Sale"]]},"2",
{$cond: [{$in:["$transactions.transactionType",["Debit"]]},"3", "4" ]}
]}
]
}
}},
So if transactionType == "Payment" add transactionTypeOrder: 1, if transactionType == "Sales" add transactionTypeOrder: 2 etc. The output I'm looking for is something like below:
Case for 2nd Case checking another field also :
In the same transactions, I also have to do another check with different field to check if "transactionStatus" field exists and if not exists add "transactionStatusOrderAsc" field as "####" and "transactionStatusOrderDsc" as "~~~" and if exist same value as "transactionStatus" So the output will be as below:
So basically, I have to check transactionType and add new Filed and also at the same time check transactionStatus and two need fields.
//result view table
{
"_id": {
"$oid": "5f61b573db16185cebfeaafd"
},
//...other fields.
"transactions": [{
"transactionType": "Payment",
"transactionTypeOrder": 1,
"transactionStatus": "New",
"transactionStatusOrderAsc": "New",
"transactionStatusOrderDsc": "New"
},
{
"transactionType": "Sales",
"transactionTypeOrder": 2,
"transactionStatusOrderAsc": "####", //since transactionStatus field doesn't exist here
"transactionStatusOrderDsc": "~~~~"
},
{
"transactionType": "Debit",
"transactionTypeOrder": 3,
"transactionStatus": "Old",
"transactionStatusOrderAsc": "Old",
"transactionStatusOrderDsc": "Old"
}
]
}
You can use $map to apply to each element of your array, and $switch to check value and add your field.
db.account.aggregate([
{
$match: {
"status": "ACTIVE"
}
},
{
$addFields: {
"accountId": {
"$toString": "$_id"
}
}
},
{
$lookup: {
from: "account_transactions",
localField: "accountId",
foreignField: "accountId",
as: "transactions"
}
},
{
"$project": {
"accountType": 1,
"createdBy": 1,
transactions: /**adds dummy fields*/
{
"$map": {
"input": "$transactions",
"as": "transactions",
"in": {
$switch: {
branches: [
{
case: {
$eq: [
"$$transactions.transactionType",
"Payment"
]
},
then: {
transactionType: "$$transactions.transactionType",
transactionTypeOrder: 1
}
},
{
case: {
$eq: [
"$$transactions.transactionType",
"Sales"
]
},
then: {
transactionType: "$$transactions.transactionType",
transactionTypeOrder: 2
}
},
{
case: {
$eq: [
"$$transactions.transactionType",
"Debit"
]
},
then: {
transactionType: "$$transactions.transactionType",
transactionTypeOrder: 3
}
},
],
default: {
transactionType: "$$transactions.transactionType",
transactionTypeOrder: -1
}
}
}
}
}
}
},
])
If you need other field of each transaction, you have to add them in each case condition.
You can test it here
EDIT FROM FIRST COMMENT
If you have to include all your transactions fields, you can use $mergeObjects instead of manually creating your object.
db.account.aggregate([
{
$match: {
"status": "ACTIVE"
}
},
{
$addFields: {
"accountId": {
"$toString": "$_id"
}
}
},
{
$lookup: {
from: "account_transactions",
localField: "accountId",
foreignField: "accountId",
as: "transactions"
}
},
{
"$project": {
"accountType": 1,
"createdBy": 1,
transactions: /**adds dummy fields*/
{
"$map": {
"input": "$transactions",
"as": "transactions",
"in": {
$switch: {
branches: [
{
case: {
$eq: [
"$$transactions.transactionType",
"Payment"
]
},
then: {
$mergeObjects: [
"$$transactions",
{
transactionTypeOrder: 1
}
]
}
},
{
case: {
$eq: [
"$$transactions.transactionType",
"Sales"
]
},
then: {
$mergeObjects: [
"$$transactions",
{
transactionTypeOrder: 2
}
]
}
},
{
case: {
$eq: [
"$$transactions.transactionType",
"Debit"
]
},
then: {
$mergeObjects: [
"$$transactions",
{
transactionTypeOrder: 3
}
]
}
},
],
default: {
$mergeObjects: [
"$$transactions",
{
transactionTypeOrder: -1
}
]
}
}
}
}
}
}
},
])
Test it here