Nested Lookup and filtering - mongodb

I have below collections
User
[
{
id : 'acd-1234',
name : 'some name',
profile_id : 1,
is_graduate: true,
children: [
{ class: 'User', id: 'abcd-123'},
{ class: 'User', id: 'bcd-33'}
]
},
{
id: 'abcd-123',
name : 'jhon',
profile_id : 2
is_graduate: true,
},
{
id: 'bcd-123',
name : 'jhon due',
profile_id : 3,
is_graduate: false
}
]
Profile
[
{
id: 1,
address: 'some address'
},
{
id: 2,
address: 'some other address'
},
{
id: 3,
address: 'some other other address'
}
]
Final Output that i need is ( Parent with only graduate children)
[
{
id: 'acd-1234',
name: 'some name',
is_graduate: true,
profile : {
id: 1,
address: "some address"
},
children: [
{
id: "abcd-123",
name: "jhon",
is_graduate: true,
profile: {
id: 2,
address: "some other address"
}
}
]
}
]
Where i am really stuck is
Making nested lookup. Showing profile with all the children
applying filtering on the childrens
Below error from mongo is not allowing me to use pipeline with localField
$lookup with 'pipeline' may not specify 'localField' or 'foreignField'

Try this:
db.users.aggregate([
{
$lookup: {
from: "profile",
let: { id: "$profile_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$id", "$$id"] }
}
}
],
as: "profile"
}
},
{ $unwind: "$profile" },
{
$lookup: {
from: "users",
let: {
id: { $ifNull: ["$children.id", []] }
},
pipeline: [
{
$match: {
$expr: { $in: ["$id", "$$id"] }
}
},
{
$lookup: {
from: "profile",
localField: "profile_id",
foreignField: "id",
as: "profile"
}
},
{ $unwind: "$profile" }
],
as: "children"
}
}
]);
If you want to show only non empty children then add below $match stage after last $lookup:
{
$match: {
$expr: {
$gt: [{ $size: "$children" }, 0]
}
}
}

Related

MongoDB aggregate match by array

is there some way to match by an array, using aggregation on mongodb?
I have the following structure:
students
{
_id: 67264264726476,
name: "Student 1",
email: "student1#email.com"
}
followups
{
_id: 65536854685,
student: 67264264726476,
message: "This is a student follow up"
}
I've made the following query:
const followups = await this.modelFollowup.aggregate([
{
$lookup: {
from: 'students',
localField: 'student',
foreignField: '_id',
as: 'student',
},
},
{ $unwind: { path: '$student' } },
{
$match: {
$or: [
{ 'student.name': { $regex: term, $options: 'i' } },
{ 'student.email': { $regex: term, $options: 'i' } },
],
},
},
]);
How can I make it possible to match using an array, instead of the term variable?
If you want to match the complete list of student names and email you can check this query
Query
db.followups.aggregate([
{
$lookup: {
from: "students",
localField: "student",
foreignField: "_id",
as: "student",
},
},
{
$unwind: {
path: "$student"
}
},
{
$match: {
$or: [
{
"student.name": {
"$in": [
"abc",
"def"
]
}
},
{
"student.email": {
"$in": [
"xyz#email.com"
]
}
},
],
},
},
])
Here is the link to the playground to check the query

$lookup to iterate specific Fields

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

aggregation lookup and match a nested array

Hello i am trying to join two collections...
#COLLECTION 1
const valuesSchema= new Schema({
value: { type: String },
})
const categoriesSchema = new Schema({
name: { type: String },
values: [valuesSchema]
})
mongoose.model('categories', categoriesSchema )
#COLLECTION 2
const productsSchema = new Schema({
name: { type: String },
description: { type: String },
categories: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'categories',
}]
})
mongoose.model('productos', productsSchema )
Now, what i pretend to do is join these collections and have an output like this.
#Example Product Document
{
name: 'My laptop',
description: 'Very ugly laptop',
categories: ['5f55949054f3f31db0491b5c','5f55949054f3f31db0491b5b'] // these are _id of valuesSchema
}
#Expected Output
{
name: 'My laptop',
description: 'Very ugly laptop',
categories: [{value: 'Laptop'}, {value: 'PC'}]
}
This is what i tried.
{
$lookup: {
from: "categories",
let: { "categories": "$categories" },
as: "categories",
pipeline: [
{
$match: {
$expr: {
$in: [ '$values._id','$$categories']
},
}
},
]
}
}
but this query is not matching... Any help please?
You can try,
$lookup with categories
$unwind deconstruct values array
$match categories id with value id
$project to show required field
db.products.aggregate([
{
$lookup: {
from: "categories",
let: { cat: "$categories" },
as: "categories",
pipeline: [
{ $unwind: "$values" },
{ $match: { $expr: { $in: ["$values._id", "$$cat"] } } },
{
$project: {
_id: 0,
value: "$values.value"
}
}
]
}
}
])
Playground
Since you try to use the non-co-related queries, I appreciate it, you can easily achieve with $unwind to flat the array and then $match. To regroup the array we use $group. The $reduce helps to move on each arrays and store some particular values.
[
{
$lookup: {
from: "categories",
let: {
"categories": "$categories"
},
as: "categories",
pipeline: [
{
$unwind: "$values"
},
{
$match: {
$expr: {
$in: [
"$values._id",
"$$categories"
]
},
}
},
{
$group: {
_id: "$_id",
values: {
$addToSet: "$values"
}
}
}
]
}
},
{
$project: {
categories: {
$reduce: {
input: "$categories",
initialValue: [],
in: {
$concatArrays: [
"$$this.values",
"$$value"
]
}
}
}
}
}
]
Working Mongo template

Aggregate $lookup Array of Objects

I have collection schools with field groups and I am attempting to $lookup documents from the users collection. I am getting empty results however and an extra schools document.
Schools Schema
const SchoolSchema = new Schema({
groups: [
{
name: { type: String },
color: { type: String },
userDrivenName: { type: String },
},
]
});
module.exports = School = mongoose.model("School", SchoolSchema);
User Schema
const UserSchema = new Schema({
name: {
type: String,
required: true,
},
groups: [
{
groupId: { type: String },
name: { type: String },
color: { type: String },
userDrivenName: { type: String },
},
]
});
Query
db.schools.aggregate([
{
$match: {
_id: ObjectId("5d836e584a24e20e6090fd7b")
}
},
{
$project: {
groups: 1
}
},
{
$unwind: "$groups"
},
{
$lookup: {
from: "users",
let: {
groupId: "$groups._id"
},
pipeline: [
{
$match: {
"groups.groupId": "$$groupId"
}
}
],
as: "groups",
},
},
])
Results:
[
{
"_id": "5d836e584a24e20e6090fd7b",
"groups": []
},
{
"_id": "5d836e584a24e20e6090fd7b",
"groups": []
}
]
Expected Results:
[
{
"_id":"5d836e584a24e20e6090fd7b",
"groups":[
{
"_id":"5ec01fdc1dfb0a4f08316dfe",
"name":"GROUP 1",
"users":[
{
"name":"Luke Skywalker"
}
]
}
]
}
]
MongoPlayground
Two things:
There's a type mismatch between groupId and groups.groupId so you need to use $toString (based on your Mongo Playground example),
$lookup with custom pipelines allows only expression when you use $match so you need $in and $expr:
{
$lookup: {
from: "users",
let: { groupId: { $toString: "$groups._id" } },
pipeline: [
{
$match: {
$expr: {
$in: ["$$groupId","$groups.groupId"]
}
}
}
],
as: "groups"
}
}
Mongo 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