How to use $lookup on embedded document field in mogodb - node.js

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" },
])

Related

How to combine 3 collections MongoDB?

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" }
}
}
])

How to make a nested relationship on mongodb?

For example i have 4 collections. There are:
"company" collection
{
"id_company": "C01"
"company_name": "Sidomuncul"
"like": [
"123",
"121"
]
}
"user" collection
{
"id_user": "123",
"name": "Astra",
"major": "111",
"language": [{
"id_language": "101",
"level": "Expert"
}]
},
{
"id_user": "121",
"name": "Bibi",
"id_major": "112",
"language": [{
"id_language": "102",
"level": "Intermediate"
}]
}
"major" collection
{
"id_major": "111",
"name": "IT"
},
{
"id_major": "112",
"name": "Designer"
}
"language" collection
{
"id_language": "101",
"name": "English"
},
{
"id_language": "102",
"name": "Chinese"
}
And when i make a route for get who are like company by id_company "C01", i want show the result relation id_user in "like" field with user collection. Example result:
{
"id_company": C01",
"like": [
{
"id_user": "123",
"name": "Astra",
"major": "IT",
"language": [{
"id_language": "English",
"level": "Expert"
}]
},
{
"id_user": "121",
"name": "Bibi",
"id_major": "Designer",
"language": [{
"id_language": "Chinese",
"level": "Intermediate"
}]
}
] //Close Like Field
}
Thanks before.
SOLVED
Use lookup and group also push
Using Mongo aggregate pipeline you can collect required result in multiple stages. For mongo aggregate query you can use $lookup, $set, $unwind and $group stages. Try this query, you might get required result set:
db.company.aggregate([
{
"$unwind": "$like"
},
{
$lookup: {
from: "user",
localField: "like",
foreignField: "id_user",
as: "like"
}
},
{
"$unwind": "$like"
},
{
$lookup: {
from: "major",
localField: "like.major",
foreignField: "id_major",
as: "major"
}
},
{
"$unwind": "$major"
},
{
$set: {
"like.major": "$major.name"
}
},
{
$lookup: {
from: "language",
localField: "like.language.id_language",
foreignField: "id_language",
as: "lang"
}
},
{
"$unwind": "$lang"
},
{
$set: {
"like.language.id_language": "$lang.name"
}
},
{
$group: {
_id: "id_company",
company_name: {
"$first": "$company_name"
},
like: {
$push: {
id_user: "$like.id_user",
major: "$like.major",
name: "$like.name",
language: "$like.language"
}
}
}
}
])

MongoDB aggregate and get mismatched results

Hi everyone i have 2 collections named "members" and "offers" in mongoDB. When a member send an offer to another, my web service saves it to offers collection.
"members" collection is like:
[
{
"_id": "5ee00pp0ebfd4432145233344",
"Fname": "John",
"Lname": "Lastname",
"Email": "JohnLastName#gmail.com",
},
{
"_id": "yyyy44p0ebfd4432145233355",
"Fname": "Ashley",
"Lname": "Lastname",
"Email": "AshleyLastName#gmail.com",
},
{
"_id": "yyyy44p0ebfd4432145233355",
"Fname": "Sue",
"Lname": "Lastname",
"Email": "SueLastName#gmail.com",
}
]
when John send an offer to Ashley
"offers" collection is like:
[
{
"_id": "5eea6e62881835271415fd25",
"OfferMail": "JohnLastName#gmail.com",
"Email": "AshleyLastName#gmail.com",
}
]
Now my question is: How can i get all members except Ashley?
Use the $not operator. For example
db.members.find({"Email" : {"$not" : "AshleyLastName#gmail.com"}})
db.members.aggregate([
{
"$lookup": {
"from": "offers",
"localField": "Email",
"foreignField": "Email",
"as": "docuentInB"
}
},
{
$match: {
$expr: { $eq: [{ $size: "$docuentInB" }, 0 ] }
}
},
{
$project: {
docuentInB: 0
}
}
])````

How to query from two collections in mongodb using aggregate?

I have the two following collections in my mongoDB:
PRODUCTS:
[
{
"_id": "5ebb0984e95e3e9e35aab3bf",
"name": "Product 1",
"brand": "ABC",
"code": "7891910000190",
"description": "Lorem Ipsum"
},
{
"_id": "5ebdb9f3d943ae000a4a9714",
"name": "Product 2",
"brand": "DEF",
"code": "7891910000190",
"description": "Lorem Ipsum"
}
]
STOCK GOODS:
[
{
"_id": "5ed0586b7f2cfe02387d0706",
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"branchId": "5ed049a8f9a892a5fd9b4d9e",
"inventoryId": "5ed04d01e9e43cee79734b95",
"productId": "5ebb0984e95e3e9e35aab3bf"
},
{
"_id": "5ed058da75e4e01013f5779d",
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"branchId": "5ed049a8f9a892a5fd9b4d9e",
"inventoryId": "5ed04cede9e43cee79734b93",
"productId": "5ebb0984e95e3e9e35aab3bf"
},
{
"_id": "5ed059483da3bafccb34cec3",
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"branchId": "5ed049a8f9a892a5fd9b4d9e",
"inventoryId": "5ed04d01e9e43cee79734b95",
"productId": "5ebb0984e95e3e9e35aab3bf"
}
]
I want to get the follow result:
[
{
"_id": "5ed0586b7f2cfe02387d0706",
"data": {
"_id": "5ebb0984e95e3e9e35aab3bf",
"brand": "ABC",
"code": "7891910000190",
"description": "Lorem Ipsum",
"name": "Product 1",
}
},
{
"_id": "5ed059483da3bafccb34cec3",
"data": {
"_id": "5ebb0984e95e3e9e35aab3bf",
"brand": "ABC",
"code": "7891910000190",
"description": "Lorem Ipsum",
"name": "Product 1",
}
}
]
My NodeJS and MongoDB aggregate look like this:
const mongoose = require("mongoose");
const StockGoods = mongoose.model("StockGoods");
ObjectId = require("mongodb").ObjectID;
exports.getProducts = async (companyId, branchId, inventoryId) => {
const response = await StockGoods.aggregate([
{
$match: {
companyId: new ObjectId("5ed0491bf9a892a5fd9b4d9b"), // new ObjectId(companyId)
inventoryId: new ObjectId("5ed04d01e9e43cee79734b95"), // new ObjectId(inventoryId)
branchId: new ObjectId("5ed049a8f9a892a5fd9b4d9e"), // new ObjectId(branchId)
},
},
{
$lookup: {
from: "products",
localField: "productId",
foreignField: "_id",
as: "inventory_docs",
},
},
{
$project: {
data: "$inventory_docs",
},
},
{ $unwind: "$data" },
]);
return response;
};
This is the same database and the aggregate function described above, you can check this: https://mongoplayground.net/p/FRgAIfO2bwh.
But, this function aggregate does not working when I use nodejs.
My API returns nothing (empty array).
Whats wrong?
Issue here is the way you have stored data in collection.... StockGoods collection has companyId, inventoryId and branchIdas a string but aggregation is looking for ObjectId aee below match criteria:
$match: {
companyId: new ObjectId("5ed0491bf9a892a5fd9b4d9b"), // new ObjectId(companyId)
inventoryId: new ObjectId("5ed04d01e9e43cee79734b95"), // new
ObjectId(inventoryId)
branchId: new ObjectId("5ed049a8f9a892a5fd9b4d9e"), // new ObjectId(branchId)
},
Based on the data you store in DB either you need to update match with string.
So below Aggregation will work for you..
[
{
"$match": {
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"inventoryId": "5ed04d01e9e43cee79734b95",
"branchId": "5ed049a8f9a892a5fd9b4d9e"
}
},
{
"$lookup": {
"from": "products",
"localField": "productId",
"foreignField": "_id",
"as": "inventory_docs"
}
},
{
"$project": {
"data": "$inventory_docs"
}
},
{
"$unwind": "$data"
}
]
This works you can test in Compass Aggregator tab
[RESOLVED]
The problem was in the scheme:
companyId: {
type: String,
required: true,
},
The correct is:
companyId: {
type: Schema.Types.ObjectId,
required: true,
},

Joining two collections in mongodb

I have separate collections for 'comments','products' and 'users'. The 'comments' collection contains text, product_id and user_id. When a product is fetched I want the details of product along with details of the user in the result.
I have created schema using mongoose odm. I am using aggregate function to populate the product with comments using $lookup.
Product.aggregate([
{
$match:{
_id:mongoose.Types.ObjectId(id)
}
},
{
$lookup: {
from: "comments",
localField: "_id",
foreignField: "product",
as: "comments"
}
},
{
$match:{
"comments.product":mongoose.Types.ObjectId(id)
}
},
{
$lookup: {
from: "users",
localField: "comments.user._id",
foreignField: "user",
as: "comments.user"
}
}
])
expected result is
[
{
"_id": "5cc9441feed4c258881c99cd",
"title": "Batman",
"imageUrl": "images\\1556694047310_Batman.jpg",
"price": 555,
"description": "The dark knight",
"user": "5cbca36d4acc5d538c209014",
"__v": 2,
"comments": [
{
"_id": "5cc947125c69600d58c1be05",
"date": "2019-05-01T07:12:42.229Z",
"text": "This product is very nice",
"user":{
"_id": "5cbca36d4acc5d538c209014",
"name": "Clark Kent"
}
},
{
"_id": "5cc96eb4b2834d43f8a24470",
"date": "2019-05-01T09:46:34.774Z",
"text": "Anyone can be Batman",
"user":{
"_id": "5cbca5504acc5d538c209015",
"name": "Bruce Wayne"
},
}
}
]
actual result is
[
{
"_id": "5cc9441feed4c258881c99cd",
"title": "Batman",
"imageUrl": "images\\1556694047310_Batman.jpg",
"price": 555,
"description": "The dark knight",
"user": "5cbca36d4acc5d538c209014",
"__v": 2,
"comments": {
"user": [
{
"_id": "5cbca5504acc5d538c209015",
"name": "Bruce Wayne",
"email": "batman#gotham.com",
"password": "$2a$12$L.t/nBXq/xlic25Y0a884uGxjlimuNH/tcmWLg.sNkcjJ/C40Q14m",
"contactNumber": 9999999999,
"address": "Somewhere in Gotham",
"__v": 0
},
{
"_id": "5cbca7334acc5d538c209016",
"name": "Superman",
"email": "superman#metro.com",
"password": "$2a$12$mrogzC1Am86b0DnvTzosm.qfu38Ue7RqSNcnVSoCR55PtmLddeZv.",
"contactNumber": 9999999999,
"address": "Somewhere in metropolis",
"__v": 0
},
{
"_id": "5cbca7e54acc5d538c209017",
"name": "Wonder Woman",
"email": "ww#amazon.com",
"password": "$2a$12$Vt9XZUyOTULvel5zNAsMLeoMi3HlaGJJZN7OH2XkWuoAiZtDIGaMq",
"contactNumber": 9999999999,
"address": "Somewhere in Amazon",
"__v": 0
},
{
"_id": "5cbe192934ae2944c8704a5a",
"name": "Barry Allen",
"email": "barry#flash.com",
"password": "$2a$12$k73Wp1HTMv/MhUV3BOok3OSh.nnLq3vWG1Qz9ZTO7iB7saFlxhLjW",
"contactNumber": 9999999999,
"address": "Somewhere in Central City",
"__v": 0
}
]
}
}
]
Your $lookup query of users is overwriting the comments array. Its not working as you think it'll.
You need to unwind the comments array and then run that $lookup of users and then group by the products.
Edit: I have updated the query with $group by code too. Also you can playa around with the query here:
https://mongoplayground.net/p/2EA-Glz8Hrm
Product.aggregate([
{
$match: {
_id: "5cc9441feed4c258881c99cd"
}
},
{
$lookup: {
from: "comments",
localField: "_id",
foreignField: "product",
as: "comments"
}
},
{
$unwind: "$comments"
},
{
$lookup: {
from: "users",
localField: "comments.user",
foreignField: "_id",
as: "comments.user"
}
},
{
$unwind: "$comments.user"
},
{
$group: {
_id: "$_id",
// add other fields you want to include
comments: {
$addToSet: "$comments"
}
}
},
])
As suggested by Hamza, I made following changes to my query
Product.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId(id)
}
},
{
$lookup: {
from: "comments",
localField: "_id", //field from input document
foreignField: "product", // field from documents of the 'from' collection
as: "comments"
}
},
{
$unwind: "$comments"
},
{
$lookup: {
from: "users",
localField: "comments.user", //field from input document
foreignField: "_id", // field from documents of the 'from' collection
as: "comments.user"
}
},
{
$unwind: "$comments.user"
},
{
$group: {
_id: "$_id",
title: { $first: "$title" }, // $first returns the first expression of the document it encounters, ex. first title
price: { $first: "$price" },
imageUrl: { $first: "$imageUrl" },
description: { $first: "$description" },
rating: { $first: "$rating" },
comments: {
$addToSet: "$comments" // group comments and create an array
}
}
},
{
$project: {
_id: 1,
title: 1,
price: 1,
imageUrl: 1,
description: 1,
rating: 1,
comments: {
_id: 1,
text: 1,
date: 1,
user: {
_id: 1,
name: 1
}
}
}
}
])
With this I got the desired result.

Resources