How to combine 3 collections MongoDB? - node.js

i have 3 collections: Restaurants, Menu and Product.
Their structure:
Restaurants
Menu
Products
I want to make a query with nested arrays. And try:
const restaurant = await Restaurant.aggregate([
{
"$match": { _id: ObjectId(req.params.id) }
},
{
"$lookup": {
"from": "menus",
"localField": "menu",
"foreignField": "_id",
"as": "menu",
}
},
{
"$lookup": {
"from": "products",
"localField": "menu.products",
"foreignField": "_id",
"as": "menu.products"
}
},
])
But this query returns data without menu name:
"menu": {
"products": [
{
"_id": "604f349280a17606402211d3",
"price": 6,
"image": "https://i.pinimg.com/736x/00/67/1e/00671e890191ecfeaf680cbecd3acf3e.jpg",
"name": "Toast with banana",
"description": "210g, Composition: Bread, Nutella, Banana",
"user": "604f349280a17606402211d0",
"__v": 0,
"createdAt": "2021-03-15T10:18:58.220Z",
"updatedAt": "2021-03-15T10:18:58.220Z"
},...]
I just started to study Mongodb, so I don't understand a lot. Please help write a request.
If I wrote something wrong, sorry, English is not my first language :)

It turned out to be done like this
const restaurant = await Restaurant.aggregate([
{
"$match": { _id: ObjectId(req.params.id) }
},
{
"$lookup": {
"from": "menus",
"localField": "menu",
"foreignField": "_id",
"as": "menu",
}
},
{ "$unwind": { "path": "$menu", "preserveNullAndEmptyArrays": true } },
{
"$lookup": {
"from": "products",
"localField": "menu.products",
"foreignField": "_id",
"as": "menu.products"
}
},
{
"$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"hours": { "$first": "$hours" },
"image": { "$first": "$image" },
"phones": { "$first": "$phones" },
"takeAway": { "$first": "$takeAway" },
"description": { "$first": "$description" },
"address": { "$first": "$address" },
"user": { "$first": "$user" },
"menu": { "$push": "$menu" }
}
}
])

Related

how to join two documents using $lookup mongodb

I have been trying to join two collection in MongoDb using the aggregate function but it seems it's not working for me, When I run api using lookup it show the me empty collection [],
I have tried with the following.
db.student.aggregate([
{
"$match": {
_id: "63c4c245188267e988d690e2"
},
},
{
"$lookup": {
"from": "wall",
"localField": "_user",
"foreignField": "_id",
"as": "wallpost"
}
}
])
Result:
Here is the result i m getting after lookup :(
[
{
"_id": "63c4c245188267e988d690e2",
"hereFor": [
{
"mainTag": "study",
"subtag": [
"studybuddy",
"findtutor"
]
}
],
"lastName": "Kumar",
"name": "Kamal",
"profilePicture": [
"https://airustudentimages.s3.ca-central-1.amazonaws.com/1673588594404-ba7777ef676f439f86aa612e8be67fd9"
],
"wallpost": []
}
]
Collections
Collection i m using in the query.
Student
student: [
{
"_id": "63c4c245188267e988d690e2",
"name": "Kamal",
"lastName": "Kumar",
"profilePicture": [
"https://airustudentimages.s3.ca-central-1.amazonaws.com/1673588594404-ba7777ef676f439f86aa612e8be67fd9"
],
"hereFor": [
{
"mainTag": "study",
"subtag": [
"studybuddy",
"findtutor"
]
}
],
},
{
"_id": "63c3965c201a1d738ab6867e",
"name": "Suraksha",
"lastName": "Singh",
"profilePicture": [
"https://airustudentimages.s3.ca-central-1.amazonaws.com/1673762449670-a8bdf9b9b0faf3ad84e0a5bc76e32fb8"
],
"hereFor": [
{
"mainTag": "study",
"subtag": [
"studybuddy",
"findtutor"
]
}
],
}
],
Wall
"wall": [
{
"_id": "63c4c92a188267e988d690e3",
"_user": "63c3965c201a1d738ab6867e",
"isSponsered": false,
"description": "Hello test case ",
"tag": {
"mainTag": "hostels"
},
"createdAt": "1673766717308",
"updatedAt": "1673766717308",
},
{
"_id": "63c4cc2b188267e988d690e5",
"_user": "63c3965c201a1d738ab6867e",
"isSponsered": false,
"description": "Hello test case 22 ",
"tag": {
"mainTag": "hostels"
},
"createdAt": "1673766717308",
"updatedAt": "1673766717308",
},
],
Have a look at https://mongoplayground.net/p/2moDXi3lygL
Your query is on Student collection. So the localField should be _id and and the foreignField should be _user (from wall collection).
Then it works fine.
db.student.aggregate([
{
"$match": {
_id: "63c3965c201a1d738ab6867e"
},
},
{
"$lookup": {
"from": "wall",
"localField": "_id",
"foreignField": "_user",
"as": "wallpost"
}
}
])
https://mongoplayground.net/p/r1JeQIlM7AA
You mix up the localField and foreignField.
From Equality Match with a Single Join Condition:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
It should be:
{
"$lookup": {
"from": "wall",
"localField": "_id",
"foreignField": "_user",
"as": "wallpost"
}
}
Demo # Mongo Playgound

MongoDB - How to return an aggregation with reference to a refence using NodeJS

I am trying to do a lookup from multiple references
Here is a Mongo Playground
Here is my data
Insp
The Insp document contains an array of references to Users (by user ID)
[
{
"_id": {
"$oid": "6359a12fb9450da3d8d8cdd2"
},
"REF_Users": [
{
"$oid": "6359a0f1b9450da3d8d8cdc7"
},
{
"$oid": "6359a070f1e84209e0c78fc2"
}
],
"name": "Once"
}
]
Users
The Users document contains information about a user and it has a reference to the UserType (by userType ID)
[
{
"_id": {
"$oid": "6359a070f1e84209e0c78fc2"
},
"REF_UserType": {
"$oid": "63596323b679475de490500a"
},
"fName": "Billy"
},
{
"_id": {
"$oid": "6359a0f1b9450da3d8d8cdc7"
},
"REF_UserType": {
"$oid": "63596323b679475de4905007"
},
"fName": "Mike"
}
]
UserType
The UserType document holds type information
[
{
"_id": {
"$oid": "63596323b679475de4905007"
},
"value": 100,
"name": "INS"
},
{
"_id": {
"$oid": "63596323b679475de490500a"
},
"value": 200,
"name": "CLS"
}
]
Expected output
I want the userType for each user to be with the respective user
{
"_id": "6359a12fb9450da3d8d8cdd2",
"people": [
{
"_id": "6359a070f1e84209e0c78fc2",
"userType": {
"_id": "63596323b679475de490500a",
"value": 200,
"name": "CLS"
},
"fName": "Billy"
},
{
"_id": "6359a0f1b9450da3d8d8cdc7",
"userType": {
"_id": "63596323b679475de4905007",
"value": 100,
"name": "INS"
},
"fName": "Mike"
}
]
}
TRY 1
This is my pipeline so far
[
{
"$match": {}
},
{
"$lookup": {
"from": "users",
"localField": "REF_Users",
"foreignField": "_id",
"as": "people"
}
},
{
"$lookup": {
"from": "usertypes",
"localField": "people.REF_UserType",
"foreignField": "_id",
"as": "userType"
}
},
{
"$project": {
"REF_Users": 0,
"people.REF_UserType": 0
}
}
]
Result of TRY 1
{
"_id": "6359a12fb9450da3d8d8cdd2",
"people": [
{
"_id": "6359a070f1e84209e0c78fc2",
"fName": "Billy"
},
{
"_id": "6359a0f1b9450da3d8d8cdc7",
"fName": "Mike"
}
],
"userType": [
{
"_id": "63596323b679475de4905007",
"value": 100,
"name": "INS"
},
{
"_id": "63596323b679475de490500a",
"value": 200,
"name": "CLS"
}
]
}
It works in Compass...
It works in the playground
When I put the code into NodeJS and run it from my server:
TRY 2
const agg_project_try = {
people: {
$map: {
input: '$people',
as: 'people',
in: {
$mergeObjects: [
'$$people',
{
userType: {
$first: {
$filter: {
input: '$userType',
cond: {
$eq: ['$$people.REF_UserType', '$$this._id'],
},
},
},
},
},
],
},
},
},
};
I get this error
Arguments must be aggregate pipeline operators
TRY 3
I exported from Compass as NODE
[
{
'$lookup': {
'from': 'users',
'localField': 'REF_Users',
'foreignField': '_id',
'as': 'people'
}
}, {
'$lookup': {
'from': 'usertypes',
'localField': 'people.REF_UserType',
'foreignField': '_id',
'as': 'userType'
}
}, {
'$project': {
'people': {
'$map': {
'input': '$people',
'as': 'people',
'in': {
'$mergeObjects': [
'$$people', {
'userType': {
'$first': {
'$filter': {
'input': '$userType',
'cond': {
'$eq': [
'$$people.REF_UserType', '$$this._id'
]
}
}
}
}
}
]
}
}
}
}
}, {
'$unset': 'people.REF_UserType'
}
]
Then tried the 'project' portion in my server
const agg_project_try = {
'people': {
'$map': {
'input': '$people',
'as': 'people',
'in': {
'$mergeObjects': [
'$$people', {
'userType': {
'$first': {
'$filter': {
'input': '$userType',
'cond': {
'$eq': [
'$$people.REF_UserType', '$$this._id'
]
}
}
}
}
}
]
}
}
}
};
I get this error
Arguments must be aggregate pipeline operators
Here is my node JS pipeline ( that causes the error )
[
{ "$match": {} },
{ "$lookup": { "from": "users", "localField": "REF_Users", "foreignField": "_id", "as": "people" } },
{ "$lookup": { "from": "usertypes", "localField": "people.REF_UserType", "foreignField": "_id", "as": "userType" } },
{
"people": {
"$map": {
"input": "$people",
"as": "people",
"in": {
"$mergeObjects": [
"$$people",
{
"userType": {
"$first": {
"$filter": { "input": "$userType", "cond": { "$eq": ["$$people.REF_UserType", "$$this._id"] } }
}
}
}
]
}
}
}
},
{ "$project": { "REF_Users": 0 } }
]
ANSWER
Up too late last night working on this stuff, actually need the "project" statement to do a projection - doh !
$project:{
'people': {
$map: {
input: '$peopleLookup',
as: 'tempPeople',
in: {
$mergeObjects: [
'$$tempPeople',
{
'userType': {
$first: {
$filter: {
input: '$userTypeLookup',
cond: {
$eq: ['$$tempPeople.REF_UserType', '$$this._id'],
},
},
},
},
},
],
},
},
},
}
Thank you!
In $project stage, you need to iterate each document from the people array`.
Merge ($merge) the current iterated document with the first ($first) filtered ($filter) result from the userType array.
db.workoutDetailSchema.aggregate([
{
"$match": {}
},
{
"$lookup": {
"from": "users",
"localField": "REF_Users",
"foreignField": "_id",
"as": "people"
}
},
{
"$lookup": {
"from": "usertypes",
"localField": "people.REF_UserType",
"foreignField": "_id",
"as": "userType"
}
},
{
"$project": {
"people": {
$map: {
input: "$people",
as: "people",
in: {
$mergeObjects: [
"$$people",
{
userType: {
$first: {
$filter: {
input: "$userType",
cond: {
$eq: [
"$$people.REF_UserType",
"$$this._id"
]
}
}
}
}
}
]
}
}
}
}
},
{
$unset: "people.REF_UserType"
}
])
Demo # Mongo Playground
I just merged the documents using javascript.
Demo#mongoplayground
db.workoutDetailSchema.aggregate([
{
"$match": {}
},
{
"$lookup": {
"from": "users",
"localField": "REF_Users",
"foreignField": "_id",
"as": "peoples"
}
},
{
"$lookup": {
"from": "usertypes",
"localField": "peoples.REF_UserType",
"foreignField": "_id",
"as": "userType"
}
},
{
$addFields: {
people: {
$function: {
body: "function (people, userType) {people.forEach(function (item, index) {if(JSON.stringify(userType[index]._id) === JSON.stringify(item.REF_UserType)){people[index].userType=userType[index];}});return people;}",
args: [
"$peoples",
"$userType"
],
lang: "js"
},
}
}
},
{
"$project": {
"REF_Users": 0,
"peoples": 0,
"userType": 0,
"people.REF_UserType": 0,
}
}
])
Output
[
{
"_id": ObjectId("6359a12fb9450da3d8d8cdd2"),
"name": "Once",
"people": [
{
"_id": ObjectId("6359a0f1b9450da3d8d8cdc7"),
"fName": "Mike",
"userType": {
"_id": ObjectId("63596323b679475de4905007"),
"name": "INS",
"value": 100
}
},
{
"_id": ObjectId("6359a070f1e84209e0c78fc2"),
"fName": "Billy",
"userType": {
"_id": ObjectId("63596323b679475de490500a"),
"name": "CLS",
"value": 200
}
}
]
}
]

How do I convert my mongoose find query to mongoDB aggregation framework?

I want to convert my existing code to mongoDB aggregation framework.
Here's my code that I want to convert in aggregation.
const comments = await Comments.find({post:req.params.id})
.populate({
path:"comments",
populate:{
path:"author",
select:"name photo"
},
select:{
createdAt:1,
author:1,
_id:1,
body:1,
likes:1
}
})
.populate("author","photo name")
.select({
createdAt:1,
author:1,
_id:1,
body:1,
comments:1,
likes:1
})
.sort("-createdAt");
You can use below aggregation
Comments.aggregate([
{ "$match": { "post": mongoose.Types.ObjectId(req.params.id) } },
{ "$sort": { "createdAt": -1 } },
{
"$lookup": {
"from": Comment.collection.name,
"let": { "comments": "$comments" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$_id", "$$comments"] } } },
{
"$lookup": {
"from": Author.collection.name,
"let": { "author": "$author" },
"pipeline": [{ "$match": { "$expr": { "$eq": ["$_id", "$$author"] } } }, { "$project": { "name": 1, "photo": 1 } }],
"as": "author"
}
},
{ "$unwind": "$author" },
{ "$project": { "createdAt": 1, "author": 1, "_id": 1, "body": 1, "likes": 1 } }
],
"as": "comments"
}
},
{
"$lookup": {
"from": Author.collection.name,
"let": { "author": "$author" },
"pipeline": [{ "$match": { "$expr": { "$eq": ["$_id", "$$author"] } } }, { "$project": { "name": 1, "photo": 1 } }],
"as": "author"
}
},
{ "$unwind": "$author" },
{
"$project": {
"createdAt": 1,
"author": 1,
"body": 1,
"comments": 1,
"likes": 1
}
}
])

Pagination on mongoDB aggregations [Node.js]

I have query something like this:
Message.aggregate([{
"$match": {
$or: [{
"to": userId
}, {
"from": userId
}]
}
},
{
"$sort": {
"createDate": -1
}
},
{
"$group": {
"_id": "$conversationId",
"from": {
"$first": "$from"
},
"to": {
"$first": "$to"
},
"content": {
"$first": "$content"
},
"createDate": {
"$first": "$createDate"
},
"unreaded": {
"$sum": {
"$cond": {
if: {
$and: [{
"$eq": [
"$unreaded", 1
]
},
{
"$eq": ["$to", userId]
}
]
},
then: 1,
else: 0
}
}
}
}
},
{
"$sort": {
"createDate": -1
}
},
{
"$lookup": {
"from": "users",
"localField": "from",
"foreignField": "_id",
"as": "from"
}
},
{
"$lookup": {
"from": "users",
"localField": "to",
"foreignField": "_id",
"as": "to"
}
},
{
"$unwind": {
"path": "$from"
}
},
{
"$unwind": {
"path": "$to"
}
},
{
"$project": {
"from.firstName": "$from.firstName",
"from.lastName": "$from.lastName",
"from.picture": "$from.picture",
"to.firstName": "$to.firstName",
"to.lastName": "$to.lastName",
"to.picture": "$to.picture",
"content": 1,
"createDate": 1,
"unreaded": 1,
"reciver": {
"$cond": {
if: {
"$eq": ["$from._id", mongoose.Types.ObjectId(userId)]
},
then: {
"firstName": "$to.firstName",
"lastName": "$to.lastName",
"_id": "$to._id"
},
else: {
"firstName": "$from.firstName",
"lastName": "$from.lastName",
"_id": "$from._id"
}
}
}
}
},
{
"$limit": 50
}
I am able now to limit records to 50 per request but problem is when I try to make pagination... I get this error when I try to add skip next to limit:
Error: Arguments must be aggregate pipeline operators
any idea how can I do that?
Check your args and correct mistake.
Message.aggregate([{"$match": {$or: [{"to": userId}, {"from": userId}]}}, ..., {$skip: 1}, {$limit: 1}])

How to use $lookup on embedded document field in mogodb

Please have a look, your help will be appriciated
var user = new Schema({
name: String,
});
var Comments = new Schema({
title : String
, body : String
,user_id : {type: Schema.Types.ObjectId, ref: 'user' }
, date : Date
});
var blog = new Schema({
author : String
, title : String
, body : String
, date : Date
, user_id :{type: Schema.Types.ObjectId, ref: 'user' }
, comments : [Comments]
});
db.blogs.aggregate([
{ $match : { "_id" : ObjectId("57e3b7f4409d80a508d52769") } },
{ $lookup: {from: "users", localField: "user_id", foreignField: "_id", as: "User"} },
])
this returns
[
{
"_id": "57e3b7f4409d80a508d52769",
"author": "Tariq",
"title": "MyfirstPost",
"body": "This is my first post",
"user_id": "57e3b763f7bc810c08f9467a",
"comments": [
{
"title": "hi",
"body": "again i am commenting on this",
"user_id": "57e3b763f7bc810c08f9467a",
"_id": "57e3c153409d80a508d5276b"
},
{
"title": "hi",
"body": "this is seond comment",
"user_id": "57e3b763f7bc810c08f9467a",
"_id": "57e3c8632ebca0ee0afb2ac6"
}
],
"__v": 0,
"User": [
{
"_id": "57e3b763f7bc810c08f9467a",
"name": "Tariq",
"username": "teekay",
"password": "123456",
"__v": 0
}
]
}
]
this return result by comparing blog table is and user table _id which is fine .. but I want to get userdetail with each comment by using user_id of “comments.user_id” blog collection and “_id” of collection
should be something like this
"_id": "57e3b7f4409d80a508d52769",
"author": "Tariq",
"title": "MyfirstPost",
"body": "This is my first post",
"user_id": "57e3b763f7bc810c08f9467a",
"comments": [
{
"title": "hi",
"body": "again i am commenting on this",
"user_id": "57e3b763f7bc810c08f9467a",
"_id": "57e3c153409d80a508d5276b",
"User": [
{
"_id": "57e3b763f7bc810c08f9467a",
"name": "Tariq",
"username": "teekay",
"password": "123456",
"__v": 0
}
]
},
You can run an aggregation operation of the pipeline:
db.blogs.aggregate([
{ "$unwind": "$comments" },
{
"$lookup": {
"from": "users",
"localField": "comments.user_id",
"foreignField": "_id",
"as": "comments.user"
}
},
{ "$unwind": "$comments.user" },
{
"$group": {
"_id": "$_id",
"author": { "$first": "$author" },
"title": { "$first": "$title" },
"body": { "$first": "$body" },
"comments": { "$push": "$comments" },
"user_id": { "$first": "$user_id" }
}
},
{
"$lookup": {
"from": "users",
"localField": "user_id",
"foreignField": "_id",
"as": "user"
}
},
{ "$unwind": "$user" },
])

Resources