Join multiple collections in mongodb and keep all fields - mongodb

I am new to NoSQL databases, and I got a little confused with collection aggregation. Here is what I am trying to do: I have three collections: productCollection, detailsCollection and brandCollection.
productCollection has the following fields: _id, name, details
detailsCollection has _id and brand
brandCollection has _id and name
These collections also have other fields, but these are the most interesting for me. As you may guess, details in productCollection is a reference to _id in detailsCollection, while brand in detailsCollection is a reference to _id in brandCollection. What I need is to get the collection with products and their brands. So, basically, I need to join these three collections and extract name from productCollection and name from brandCollection
So far, I managed to write this script:
db.productCollection.aggregate([
{
$lookup: {
from: "detailsCollection",
localField: "details",
foreignField: "_id",
as: "det"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$det", 0 ] }, "$$ROOT" ] } }
},
{ $project: { "det": 0 } },
{
$lookup: {
from: "brandCollection",
localField: "brand",
foreignField: "_id",
as: "br"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$br", 0 ] }, "$$ROOT" ] } }
},
{ $project: { "br": 0 } }
])
It shows me all the fields in all three collections, but it does not show me the brand's name. I think it might be because the field name appears in both productCollection and brandCollection. All other fields are fine.
Hence, my question is: how do I make name from brandCollection appear in the result too? Maybe I can rename it in the process to be shown under another name? And is there an easier way to join these three collections? Or is the script above fine?
Thank you for any help!

$lookup with detailsCollection collection
$lookup with brandCollection and pass localField as brand id
$arrayElemAt to get first element from brand result
remove details field its no longer needed
db.productCollection.aggregate([
{
$lookup: {
from: "detailsCollection",
localField: "details",
foreignField: "_id",
as: "brand"
}
},
{
$lookup: {
from: "brandCollection",
localField: "brand.brand",
foreignField: "_id",
as: "brand"
}
},
{
$addFields: {
brand: {
$arrayElemAt: ["$brand.name", 0]
},
details: "$$REMOVE"
}
}
])
Playground

Related

MongoDB Query to find the list of customers who has SELL and PURCHASE Both

I am using mongoDB as NoSql Database. I have two collections One is CUSTOMER(CID,CNAME) and Other one is SHOP(Bill_No, Type, Amount,CID)
In SHOP Collection, CID is referenced with Customer(CID).Also Type can be either "SELL" or "PURCHASE".
db.CUSTOMER.insertOne({"CID":1,"CNAME":"Mark"});
db.CUSTOMER.insertOne({"CID":2,"CNAME":"Chris"});
db.CUSTOMER.insertOne({"CID":3,"CNAME":"James"});
db.SHOP.insertOne({"Bill_No":1,"TYPE":"SELL","Amount":1000,"CID":1});
db.SHOP.insertOne({"Bill_No":2,"TYPE":"SELL","Amount":350,"CID":2});
db.SHOP.insertOne({"Bill_No":3,"TYPE":"PURCHASE","Amount":450,"CID":1});
db.SHOP.insertOne({"Bill_No":4,"TYPE":"PURCHASE","Amount":360,"CID":3});
db.SHOP.insertOne({"Bill_No":5,"TYPE":"SELL","Amount":800,"CID":3});
What should be query to find the list of customers who has SELL and PURCHASE Both.
So according to given data the output should be like
"CID":1, "NAME":"MARK"
"CID":3, "NAME":"JAMES"
Thanks in advance..
Demo - https://mongoplayground.net/p/ZXylHb2g7Dm
Get all details linked to CUSTOMER from SHOP using $
lookup
Use $group combine all types into 1 document
To match both types "PURCHASE", "SELL" use $all
db.CUSTOMER.aggregate([
{
$lookup: {
from: "SHOP",
let: { c_id: "$CID" },
pipeline: [
{ $match: { $expr: {$eq: [ "$CID", "$$c_id" ] } } },
{ $group: { _id: null, types: { "$addToSet": "$TYPE" } } },
{ $match: { types: { $all: [ "PURCHASE", "SELL" ] } } }
],
as: "trades"
}
},
{ $match: { "trades.types.0": { $exists: true } } },
{ $project: { _id: 0, CID: 1, CNAME: 1 } }
])
You can use $lookup for aggregation, click here for reference.
Syntax
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
In your case the query would be
db.CUSTOMER.aggregate([
{
$lookup:
{
from: "SHOP",
localField: "CID",
foreignField: "CID",
as: "SELL_PURCHASE"
}
}
])

Mongo Aggregation - Join two collections based on two fields and get merged document

For example, let's say you have a users collection and a comments collection and you want to join two collections based on userId and firstName field using lookup and get merged document.
Let's say the users collection has the following fields
_id
userId
firstName
lastName
country
gender
age
And then the comments collection has the following fields:
_id
userId
firstName
comment
created
And the query I have is
db.getCollection('users').aggregate([ { $lookup: { from: "comments", localField: "userId", foreignField: "userId", as: "usersInfo" } }, { $lookup: { from: "comments", localField: "firstName", foreignField: "firstName", as: "commentsInfo" } }])
How do I join using two field keys - userId and firstName
Please help on this.
You need to use conditional lookup with pipeline:
[{$lookup: {
from: "comments",
let: { userId: "$userId", firstName: "$firstName" },
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$userId", "$$userId" ] },
{ $eq: [ "$firstName", "$$firstName" ] }
]
}
}
}
],
as: "user_comments"
}
}]
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/

mongo join two collections and use aggregation

im trying to get only those products for whom no order exists, so each product has an order id, these audit tables were linked to orders, but those orders are now deleted, i need to locate those products with no orders.
I know when doing aggregates if the joining collection has no records its not returning anything as "docs", but how can i get it to return me docs == empty or null only..
db.products.aggregate([
{
$match: {
$and: [
{ "docs": { $exists: false } }
]
}
},
{
$lookup: {
from: "orders",
localField: "orderId",
foreignField: "orderId",
as: "docs"
}
},
{
$unwind:
{
path: "$docs",
preserveNullAndEmptyArrays: true
}
},
{ $limit: 10 }
]).pretty()
db.products.aggregate([
{
$lookup: {
from: "orders",
localField: "orderId",
foreignField: "orderId",
as: "docs"
}
},
{ $match: { docs: [] },
{ $limit: 10 }
]).pretty()

lookup with condition in mongoose

I have two collections. articles and bookmarks.
articles
{
_id: "5faa889ade5e0a6326a873d3",
name: "article 1"
},
{
_id: "5faa889ade5e0a6326a873d",
name: "article 2"
}
bookmarks
{
_id: "5faa889ade5e0a6326a873d1",
user_id: "5fc7b50da483a66a86aa7e9e",
model_id: "5faa889ade5e0a6326a873d3"
}
I want to join article with bookmark. if user bookmarked a article.
what i have tried
const aggregate = await Articles.aggregate([{
$lookup: {
from: "categories",
localField: "category_id",
foreignField: "_id",
as: "category_id"
}
},
{
$lookup: {
from: "bookmarks",
localField: "_id",
foreignField: "model_id",
as: "bookmarks"
}
}
]);
but it will gives all bookmark for the article not only logged in user bookmark. so how can I add a condition.
{ "user_id": objectId(req.user._id) } // logged user id
You can use $lookup with pipeline starting from MongoDB v3.6,
let to pass localField _id as model_id variable, you can use the field inside lookup pipeline using $$ reference,
pipeline to put $match stage and match your required conditions and user_id condition
{
$lookup: {
from: "bookmarks",
let: { model_id: "$_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$$model_id", "$model_id"] },
user_id: objectId(req.user._id)
}
}
],
as: "bookmarks"
}
}
Other option for MongoDB v3.4,
$filter to iterate loop of bookmarks and get filtered bookmarks on the base of condition
{
$lookup: {
from: "bookmarks",
localField: "_id",
foreignField: "model_id",
as: "bookmarks"
}
},
{
$addFields: {
bookmarks: {
$filter: {
input: "$bookmarks",
cond: { $eq: ["$$this.user_id", objectId(req.user._id)] }
}
}
}
}
You can have nested pipeline inside $lookup,
db.articles.aggregate([
{
$lookup: {
from: "bookmarks",
let: {
article_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$model_id",
"$$article_id"
]
},
{
$eq: [
"$user_id",
"5fc7b50da483a66a86aa7e9a"
]
}
]
}
}
}
],
as: "bookmarks"
}
}
])
Here's a working playground

The Foreign field of $lookup could be the field of nested document?

$lookup is used to perform a left outer join to an unsharded collection in the same database to filter in documents from the “joined” collection for processing in Mongo.
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
Could the foreignField be the field of the nested document of from collection?
For example, there are two collections as following.
history collection
[{
id:'001',
history:'today worked',
child_id:'ch001'
},{
id:'002',
history:'working',
child_id:'ch004'
},{
id:'003',
history:'now working'
child_id:'ch009'
}],
childsgroup collection
[{
groupid:'g001', name:'group1'
childs:[{
id:'ch001',
info:{name:'a'}
},{
id:'ch002',
info:{name:'a'}
}]
},{
groupid:'g002', name:'group1'
childs:[{
id:'ch004',
info:{name:'a'}
},{
id:'ch009',
info:{name:'a'}
}]
}]
so, this aggregation code could be executed like this?
db.history.aggregate([
{
$lookup:
{
from: "childsgroup",
localField: "child_id",
foreignField: "childs.$.id",
as: "childinfo"
}
}
])
So I want to get the result like this.
[{
id:'001',
history:'today worked',
child_id:'ch001',
childinfo:{
id:'001',
history:'today worked',
child_id:'ch001'
}
}, .... ]
Isn't this possible?
There's no positional operator for $lookup but you can use custom pipeline in MongoDB 3.6 to define custom join conditions:
db.history.aggregate([
{
$lookup: {
from: "childsgroup",
let: { child_id: "$child_id" },
pipeline: [
{ $match: { $expr: { $in: [ "$$child_id", "$childs.id" ] } } },
{ $unwind: "$childs" },
{ $match: { $expr: { $eq: [ "$childs.id", "$$child_id" ] } } },
{ $replaceRoot: { newRoot: "$childs" } }
],
as: "childInfo"
}
}
])
First $match added to improve performance: we want to find only those documents from childsgroup that contain matching child_id and then we can match subdocuments after $unwind stage.