$lookup to iterate specific Fields - mongodb

I am trying to join specific fields from product while performing all request in addtocart. I don't know how to update lookup for this requirement. below I have updated my product collection and add to cart collection. Can anyone suggest me how to do this?
Add to Cart Collection:
add_to_cart_products: [
{
product: ObjectId('5f059f8e0b4f3a5c41c6f54d'),
product_quantity: 5,
product_item: ObjectId('5f4dddaf8596c12de258df20'),
},
],
add_to_cart_product_total: 5,
add_to_cart_discount: 50,
Product Collection:
{
_id: ObjectId('5f059f8e0b4f3a5c41c6f54d'),
product_name: 'La Gioiosa Prosecco',
product_description: 'No Description',
product_overview: 'No Overview',
product_items: [
{
product_item_number: '781239007465',
product_price: 14.99,
product_images: ['pro03655-781239007465-1.png'],
product_item_is_active: true,
_id: ObjectId('5f4dddaf8596c12de258f021'),
},
{
product_item_number: '850651005110',
product_price: 12.99,
product_images: ['default.png'],
product_item_is_active: true,
_id: ObjectId('5f4dddaf8596c12de258df20'),
},
],
product_created_date: ISODate('2020-07-08T10:29:05.892Z'),
product_status_is_active: true,
},
In my AddToCart Schema Lookup
lookups: [
{
from: 'shop_db_products',
let: { productId: '$add_to_cart_products.product', purchaseQuantity: '$add_to_cart_products.product_quantity' },
pipeline: [
{
$match: { $expr: { $in: ['$_id', '$$productId'] } },
},
{
$lookup: {
from: 'shop_db_products',
localField: 'product_id',
foreignField: '_id',
as: 'product',
},
},
{
$project: {
product_id: '$$productId',
product_purchase_quantity: '$$purchaseQuantity',
product_name: true,
},
},
{
$unwind: '$product_id',
},
{
$unwind: '$product_purchase_quantity',
},
],
as: 'add_to_cart_products',
model: 'ProductModel',
},
],
Current Result:
"add_to_cart_products": [
{
"product_name": "Avery Coconut Porter",
"product_id": "5f059f8e0b4f3a5c41c6f54d",
"product_purchase_quantity": 5
}
],
"add_to_cart_product_total": 5,
"add_to_cart_discount": 50,
Expected Result:
"add_to_cart_products": [
{
"product_name": "Avery Coconut Porter",
"product_id": "5f059f8e0b4f3a5c41c6f54d",
"product_item":[
"product_price": 12.99,
"product_images": ["default.png"],
],
"product_purchase_quantity": 5
}
],
"add_to_cart_product_total": 5,
"add_to_cart_discount": 50,

You can try,
$unwind deconstruct add_to_cart_products array
$lookup with shop_db_products collection pass required fields in let
$match productId equal condition
$project to show required fields, and get product item from array product_items using $filter to match product_item_id, and $reduct to get specific fields from product_item
$unwind deconstruct add_to_cart_products array
$group by _id and get specific fields and construct add_to_cart_products array
db.add_to_cart.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",
product_item_id: "$add_to_cart_products.product_item"
},
pipeline: [
{ $match: { $expr: { $eq: ["$_id", "$$productId"] } } },
{
$project: {
product_name: 1,
product_id: "$_id",
product_purchase_quantity: "$$purchaseQuantity",
product_item: {
$reduce: {
input: {
$filter: {
input: "$product_items",
cond: { $eq: ["$$product_item_id", "$$this._id"] }
}
},
initialValue: {},
in: {
product_price: "$$this.product_price",
product_images: "$$this.product_images"
}
}
}
}
}
],
as: "add_to_cart_products"
}
},
{ $unwind: "$add_to_cart_products" },
{
$group: {
_id: "$_id",
add_to_cart_discount: { $first: "$add_to_cart_discount" },
add_to_cart_product_total: { $first: "$add_to_cart_product_total" },
add_to_cart_products: { $push: "$add_to_cart_products" }
}
}
])
Playground

Related

Mongo query for lookup array of keys which is itself an item in a nested array

My first collection is as below, I am searching the document with the email and match the particular jobid inside the jobs array. Then insert the document of second collection by matching _id with jobs.Process.profile_id.
{
"_id": {
"$oid": "6229d3cfdbfc81a8777e4821"
},
"jobs": [
{
"job_ID": {
"$oid": "62289ded8079821eb24760e0"
},
"Process": [
{
"profile_id": {
"$oid": "6285e571681188e83d434797"
}
},
{
"profile_id": {
"$oid": "6285e571681188e83d434799"
}
}
],
},
{
"job_ID": {
"$oid": "6228a252fb4554dd5c48202a"
},
"Process": [
{
"profile_id": {
"$oid": "62861067dc9771331e61df5b"
}
}
],
},
{
"job_ID": {
"$oid": "622af1c391b290d34701af9f"
},
"Process": [
""
],
}
],
"email": "********#gmail.com"
}
and my second collection is, I need to insert this document in my first collection by matching with jobs.Process.profile_id.
{
"_id": {
"$oid": "6285e571681188e83d434797"
},
"Name": "Lakshdwanan",
"Location":"California"
}
I have tried with query,
aggregate([
{ $match: { email: email } },
{
$lookup: {
from: 'user__profiles',
localField: 'jobs.Process.profile_id',
foreignField: '_id',
as: 'jobings',
},
},
{
$addFields: {
jobings: {
$map: {
input: {
$filter: {
input: '$jobs',
as: 'm',
cond: {
$eq: ['$$m.job_ID', objInstance],
},
},
},
as: 'm',
in: {
$mergeObjects: [
{
$arrayElemAt: [
{
$filter: {
input: '$jobings',
cond: {
$eq: ['$$this._id', '$$m.Process.profile_id'],
},
},
},
0,
],
},
'$$m',
],
},
},
},
},
},
{
$project: {
jobings: 1,
_id: 0,
},
},
]);
My output should only display second collection document based on the first collection document matching.
EDIT: If you want the data for a specific job only, it is better to $filter the jobs before the $lookup step. After the $lookup, just $unwind and format:
db.firstCol.aggregate([
{
$match: {email: email}
},
{
$project: {
jobs: {
$filter: {
input: "$jobs",
as: "item",
cond: {$eq: ["$$item.job_ID", objInstance]}
}
},
_id: 0
}
},
{
$lookup: {
from: "user__profiles",
localField: "jobs.Process.profile_id",
foreignField: "_id",
as: "jobings"
}
},
{
$project: {res: "$jobings", _id: 0}
},
{
$unwind: "$res"
},
{
$replaceRoot: {newRoot: "$res"}
}
])
Playground
The jobs.Process.profile_id is the user__profiles _id, so no need to merge anything...The results are documents from user__profiles collection "as is" but they can be formatted as wanted..._id key name can be renamed profile_id easily.

MongoDB: Optimal joining of one to many relationship

Here is a hypothetical case of orders and products.
'products' collection
[
{
"_id": "61c53eb76eb2dc65de621bd0",
"name": "Product 1",
"price": 80
},
{
"_id": "61c53efca0a306c3f1160754",
"name": "Product 2",
"price": 10
},
... // truncated
]
'orders' collection:
[
{
"_id": "61c53fb7dca0579de038cea8", // order id
"products": [
{
"_id": "61c53eb76eb2dc65de621bd0", // references products._id
"quantity": 1
},
{
"_id": "61c53efca0a306c3f1160754",
"quantity": 2
},
]
}
]
As you can see, an order owns a list of product ids. When I pull an order's details I also need the product details combined like so:
{
_id: ObjectId("61c53fb7dca0579de038cea8"),
products: [
{
_id: ObjectId("61c53eb76eb2dc65de621bd0"),
quantity: 1,
name: 'Product 1',
price: 80
},
{
_id: ObjectId("61c53efca0a306c3f1160754"),
quantity: 2,
name: 'Product 2',
price: 10
},
... // truncated
]
}
Here is the aggregation pipleline I came up with:
db.orders.aggregate([
{
$match: {_id: ObjectId('61c53fb7dca0579de038cea8')}
},
{
$unwind: {
path: "$products"
}
},
{
$lookup: {
from: 'products',
localField: 'products._id',
foreignField: '_id',
as: 'productDetail'
}
},
{
$unwind: {
path: "$productDetail"
}
},
{
$group: {
_id: "$_id",
products: {
$push: {$mergeObjects: ["$products", "$productDetail"]}
}
}
}
])
Given how the data is organized I'm doubting if the pipeline stages are optimal and could do better (possibility of reducing the number of stages, etc.). Any suggestions?
As already mentioned in comments the design is poor. You can avoid multiple $unwind and $group, usually the performance should be better with this:
db.orders.aggregate([
{ $match: { _id: "61c53fb7dca0579de038cea8" } },
{
$lookup: {
from: "products",
localField: "products._id",
foreignField: "_id",
as: "productDetail"
}
},
{
$project: {
products: {
$map: {
input: "$products",
as: "product",
in: {
$mergeObjects: [
"$$product",
{
$first: {
$filter: {
input: "$productDetail",
cond: { $eq: [ "$$this._id", "$$product._id" ] }
}
}
}
]
}
}
}
}
}
])
Mongo Playground

Adding remaining filed in $project after adding lookups in mongoose

I'm doing a $lookup from an _id in Order schema, and its working as expected. But in $project how to add remaining keys. I have added my code below.
Product Collection:
{
"_id": "54759eb3c090d83494e2d804",
"product_name": "sample product",
"image": "default.png",
"price": 55,
"discount": 5,
}
Order list Collection
{
"user_name": "sample1",
"product_list":[
{
"product_id": "54759eb3c090d83494e2d804"
"quantity": 5
}
]
}
lookups
[
{
from: 'product',
localField: 'product_list.product_id',
foreignField: '_id',
as: 'product_list.product_id',
model: 'ProductModel',
},
],
$Project
{
user_name: true,
product_list: {
$map: {
input: '$product_list.product_id',
as: 'product',
in: {
product_name: '$$product.product_name',
},
},
},
}
Current Result:
{
"user_name": "sample1",
"product_list":[
"product_id":{
"product_name": "sample product"
}
]
}
In this current result, the quantity field is missing. How to add in $project?. The expected result shown below
Expected Result:
{
"user_name": "sample1",
"product_list":[
{
"product_id": {
"product_name": "sample product"
}
"quantity": 5
}
]
}
You need to do $unwind before $lookup, because it will not work directly in array fields, and here you don't need $map inside $project,
$unwind product_list deconstruct array
db.order.aggregate([
{ $unwind: "$product_list" },
$lookup with pipeline, this will allow to use pipeline inside lookup, here $project to required fields
{
$lookup: {
from: "product",
as: "product_list.product_id",
let: { product_id: "$product_list.product_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$$product_id", "$_id"] }
}
},
{
$project: {
_id: 0,
product_name: 1
}
}
]
}
},
$unwind with path product_list.product_id because you need it as object
{ $unwind: { path: "$product_list.product_id" } },
$group by _id re-construct your product_list array
{
$group: {
_id: "$_id",
user_name: { $first: "$user_name" },
product_list: { $push: "$product_list" }
}
}
])
Playground

Multiple $lookup and sort nested arrays in MongoDB

I am learning mongodb, i searched for some tips at whole internet, but i still cant cant get the proper result.
The only thing i have to do is join 2 collections.
Let me introduce the problem.
COLLECTIONS
Artists
{
_id: 1,
Name: 'Artists one'
}
Albums
{
_id: 1,
title: "Album 01",
year
artists_id: 1
}
{
_id: 2,
title: "Album 02",
year: 2020,
artists_id: 1
}
Tracks
{
albums_id: 1,
track_number: 1,
title: 'Track 01',
time: 123
}
{
albums_id: 1,
track_number: 2,
title: 'Track 02',
time: 123
}
{
albums_id: 2,
track_number: 1,
title: 'Track 01',
time: 123
}
{
albums_id: 2,
track_number: 2,
title: 'Track 02',
time: 123
}
WHAT I WANT TO ACHIVE ?
a query should return result like below.
Albums should be sorted by year ascending.
Tracks should be sorted by track_number ascending (or descending whatever i wish)
{
Name: 'Artists one',
Albums: [
{
title: "Album 01",
tracks: [
{
title: 'Track 01'
},
{
title: 'Track 02'
}
]
},
{
title: "Album 02",
tracks: [
{
title: 'Track 01'
},
{
title: 'Track 02'
}
]
}
]
}
WHAT I END UP WITH ?
I can successfully print all data with sorted albums, but i don't know how to unwind tracks to sort them by track_number and group it again like in code up
db.artists.aggregate([
{
$lookup:
{
from: "albums",
localField: "_id",
foreignField: "artists_id",
as: "albums"
}
},
{
$unwind: "$albums"
},
{
$lookup:
{
from: "tracks",
localField: "albums._id",
foreignField: "albums_id",
as: "albums.tracks"
}
},
{
$sort:
{
"albums.year": 1
}
},
{
$group:
{
_id : "$_id",
"Name" : { $first: "$Name" },
albums: { $push: "$albums" }
}
},
{
$project:
{
"_id":0,
"Name":1,
"albums": {"title":1, "tracks": {"title":1}}
}
}
]).pretty()
WHAT I NEED
I know it can't be hard, i just still try to understund the aggregation framework. I will be really greatfull if someone can show me how to make this work - also if u can additionaly explain how to achive result consistent with the assumptions i mentioned before but with look:
{
Name: 'Artists one',
Albums: [
{
title: "Album 01",
tracks: ['Track 01' 'Track 02']
},
{
title: "Album 02",
tracks: ['Track 01' 'Track 02']
}
]
}
The code just would help me very much in understanding aggregation framework.
Because of sorting the query is a bit complex
db.artists.aggregate([
{
$lookup: {
from: "albums",
localField: "_id",
foreignField: "artists_id",
as: "albums"
}
},
{
$unwind: "$albums"
},
{
$lookup: {
from: "tracks",
localField: "albums._id",
foreignField: "albums_id",
as: "albums.tracks"
}
},
{
$unwind: "$albums.tracks"
},
{
$sort: {
"albums.tracks.track_number": 1
}
},
{
$group: {
_id: {
_id: "$_id",
Name: "$Name",
albumId: "$albums._id",
albumTitle: "$albums.title",
albumYear: "$albums.year"
},
albumsTracks: {
$push: "$albums.tracks"
}
}
},
{
$project: {
_id: "$_id._id",
Name: "$_id.Name",
albumId: "$_id.albumId",
albumTitle: "$_id.albumTitle",
albumYear: "$_id.albumYear",
tracks: "$albumsTracks"
}
},
{
$sort: {
albumYear: 1
}
},
{
$group: {
_id: {
_id: "$_id",
Name: "$Name"
},
Albums: {
$push: {
title: "$albumTitle",
tracks: "$tracks"
}
}
}
},
{
$project: {
_id: "$_id._id",
Name: "$_id.Name",
"Albums.tracks.title": 1
}
}
]).pretty()
In general, if you see such a overhead, it's a signal to think different structure to store your data. For example you may want to combine all data in one collection if you're sure single record won't exceed 16 mb at some point of time.
Since MongoDB 3.6, you can use conditional $lookup. For each albums, you fetch tracks.
db.artists.aggregate([
{
$lookup: {
from: "albums",
let: {
"artists_id": "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$artists_id",
"$$artists_id"
]
}
}
},
{
$sort: {
year: 1
}
},
{
$lookup: {
from: "tracks",
let: {
"albums_id": "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$albums_id",
"$$albums_id"
]
}
}
},
{
$sort: {
track_number: 1
}
}
],
as: "tracks"
}
},
{
$project: {
_id: 0,
title: 1,
tracks: {
$map: {
input: "$tracks",
in: "$$this.title"
}
}
}
}
],
as: "Albums"
}
},
{
$unset: "_id"
}
])
MongoPlayground

How to do lookup on an aggregated collection in mongodb that is being grouped?

For some reason, I can't retrieve the author name from another collection on my aggregate query.
db.getCollection('books').aggregate([
{
$match: {
authorId: { $nin: [ObjectId('5b9a008575c50f1e6b02b27b'), ObjectId('5ba0fb3275c50f1e6b02b2f5'), ObjectId('5bc058b6ae9a2a4d6df330b1')]},
isBorrowed: { $in: [null, false] },
status: 'ACTIVE',
},
},
{
$lookup: {
from: "authors",
localField: "authorId", // key of author id in "books" collection
foreignField: "_id", // key of author id in "authors" collection
as: "bookAuthor",
}
},
{
$group: {
_id: {
author: '$authorId',
},
totalSalePrice: {
$sum: '$sale.amount',
},
},
},
{
$project: {
author: '$_id.author',
totalSalePrice: '$totalSalePrice',
authorName: '$bookAuthor.name', // I can't make this appear
_id: 0,
},
},
{ $sort: { totalSalePrice: -1 } },
])
Any advice on where I had it wrong? Thanks for the help.
Two things that are missing here: you need $unwind to convert bookAuthor from an array into single object and then you need to add that object to your $group stage (so that it will be available in next stages), try:
db.getCollection('books').aggregate([
{
$match: {
authorId: { $nin: [ObjectId('5b9a008575c50f1e6b02b27b'), ObjectId('5ba0fb3275c50f1e6b02b2f5'), ObjectId('5bc058b6ae9a2a4d6df330b1')]},
isBorrowed: { $in: [null, false] },
status: 'ACTIVE',
},
},
{
$lookup: {
from: "authors",
localField: "authorId",
foreignField: "_id",
as: "bookAuthor", // this will be an array
}
},
{
$unwind: "$bookAuthor"
},
{
$group: {
_id: {
author: '$authorId',
},
bookAuthor: { $first: "$bookAuthor" },
totalSalePrice: {
$sum: '$sale.amount',
},
},
},
{
$project: {
author: '$_id.author',
totalSalePrice: '$totalSalePrice',
authorName: '$bookAuthor.name',
_id: 0,
},
},
{ $sort: { totalSalePrice: -1 } },
])
Actually you have lost the bookAuthor field in the $group stage. You have to use $first accumulator to get it in the next $project stage.
{ "$group": {
"_id": { "author": "$authorId" },
"totalSalePrice": { "$sum": "$sale.amount" },
"authorName": { "$first": "$bookAuthor" }
}},
{ "$project": {
"author": "$_id.author",
"totalSalePrice": "$totalSalePrice",
"authorName": { "$arrayElemAt": ["$bookAuthor.name", 0] }
"_id": 0,
}}