How join two collections between Objects "_id" in MongoDB - node.js

regards, I am trying to merge two collections.
Products
Categories
The point is that its only relationship is the ObjectId of the corresponding document, see:
PRODUCT COLLECTION
{
"_id": Object(607a858c2db9a42d1870270f),
"code":"CODLV001",
"name":"Product Name",
"category":"607a63e5778bf40cac75d863",
"tax":"0",
"saleValue":"0",
"status":true
}
CATEGORY COLLECTION
{
"_id": Object(607a63bf06e84e5240d377de),
"name": "Diversey Care",
"status": true
},
{
"_id": Object(607a63e5778bf40cac75d863),
"name": "Sani Tisu Profesional",
"status": true
}
WHAT I'M DOING
.collection(collection)
.aggregate([
{
$lookup: {
from: 'categories',
localField: 'products.category',
foreignField: 'categories._id',
as: 'category',
},
},
])
.toArray();
WHAT AM I GETTING?
{
_id: 607a858c2db9a42d1870270f,
code: 'CODLV001',
name: 'Product Name',
category: [ [Object], [Object] ],
tax: '0',
saleValue: '0'
}
WHAT I EXPECT?
{
_id: 607a858c2db9a42d1870270f,
code: 'CODLV001',
name: 'Product Name',
category: [ [Object] ], // or every field from category collection without an array object
tax: '0',
saleValue: '0'
}
But, if I use this way, the category field is an empty array
{
$lookup: {
from: 'categories',
localField: 'category',
foreignField: '_id',
as: 'category',
},
},
So, what i'm doing wrong (just in case I'm new in the wolrd of MongoDB)?

There are few fixes in your query,
products collection field category is string type and categories field _id is objectId type so we need to convert it to objectId using $toObjectId
$lookup, pass localField as category and pass foreignField as _id, there is no need to concat collection name
.collection(collection).aggregate([
{
$addFields: {
category: {
$toObjectId: "$category"
}
}
},
{
$lookup: {
from: "categories",
localField: "category",
foreignField: "_id",
as: "category"
}
}
]).toArray();
Playground

Related

How to get the first element from a child lookup in aggregation - Mongoose

I'm trying to find all the docs from groupUserRoleSchema with a specific $match condition in the child. I'm getting the expected result, but the child application inside groupSchema is coming as an array.
I just need the first element from the application array as an object. How to convert this application into a single object.
These are my models
const groupUserRoleSchema = new mongoose.Schema({
group: {
type: mongoose.Schema.Types.ObjectId,
ref: 'group'
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
}
});
const groupSchema = new mongoose.Schema({
application: {
type: mongoose.Schema.Types.ObjectId,
ref: 'application'
}
});
Here is my aggregate condition.
groupUserRoleModel.aggregate([
{
$lookup: {
from: "groups", //must be PHYSICAL collection name
localField: "group",
foreignField: "_id",
as: "group",
}
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "user",
}
},
{
$lookup: {
from: "applications",
localField: "group.application",
foreignField: "_id",
as: "group.application",
}
},
{
$addFields: {
group: {
$arrayElemAt: ["$group", 0],
},
user: {
$arrayElemAt: ["$user", 0],
}
},
},
{
$match: {
"user.email_id": requestHeaders.user_email
},
}
]);
Here the $lookup group.application is coming as an array. Instead i need it as an object.
Below added is the current output screen-shot
Here is the expected output screen-shot
Any suggestions ?
A alternative is re-writing the main object return, and merge the fields.
The lookup field, gonna use the $arrayElemAt attribute at position 0, with the combination of the root element '$$ROOT'.
const result = await groupUserRoleModel.aggregate([
{
$match: {
"user.email_id": requestHeaders.user_email //The Match Query
},
},
{
$lookup: {
from: 'groups', // The collection name
localField: 'group',
foreignField: '_id',
as: 'group', // Gonna be a group
},
},
{
// Lookup returns a array, so get the first once it is a _id search
$replaceRoot: {
newRoot: {
$mergeObjects: [ // merge the object
'$$ROOT',// Get base object
{
store: {
$arrayElemAt: ['$group', 0], //Get first elemnt
},
},
],
},
},
},
]);

Combining $lookup aggregation inside updateMany?

I have a collection of users like this
[
{ _id: ObjectId("61a6d586e56ea12d6b63b68e"), fullName: "Mr A" },
{ _id: ObjectId("6231a89b009d3a86c788bf39"), fullName: "Mr B" },
{ _id: ObjectId("6231a89b009d3a86c788bf3a"), fullName: "Mr C" }
]
And a collection of complains like this
[
{ _id: ObjectId("6231aaba2a038b39d992099b"), type: "fee", postedBy: ObjectId("61a6d586e56ea12d6b63b68e" },
{ _id: ObjectId("6231aaba2a038b39d992099b"), type: "fee", postedBy: ObjectId("6231a89b009d3a86c788bf3c" },
{ _id: ObjectId("6231aaba2a038b39d992099b"), type: "fee", postedBy: ObjectId("6231a89b009d3a86c788bf3b" },
]
I want to check if the postedBy fields of complains are not existed in users, then update by using the updateMany query
By the way, I have an optional way to achieve the goal but must use 2 steps:
const complains = await Complain.aggregate()
.lookup({
from: "users",
localField: "postedBy",
foreignField: "_id",
as: "postedBy",
})
.match({
$expr: {
$eq: [{ $size: "$postedBy" }, 0],
},
});
complains.forEach(async (complain) => {
complain.type = "other";
await complain.save();
});
Therefore, can I combine 2 steps into a single updateMany query? Like $match and $lookup inside updateMany query?
With MongoDB v4.2+, you can use $merge to perform update at last stage of aggregation.
db.complains.aggregate([
{
"$lookup": {
from: "users",
localField: "postedBy",
foreignField: "_id",
as: "postedByLookup"
}
},
{
$match: {
postedByLookup: []
}
},
{
"$addFields": {
"type": "other"
}
},
{
"$project": {
postedByLookup: false
}
},
{
"$merge": {
"into": "complains",
"on": "_id",
"whenMatched": "replace"
}
}
])
Here is the Mongo playground for your reference.

aggregate multiple collections with same field mongo

I want to aggregate Collection A with Collection B and C.
Collection A's _id is saved in collection B and C as ret_id.
I tried following:
await col_A.aggregate([
{
$lookup: {
from: "col_B",
localField: "_id",
foreignField: "ret_id",
as: "task",
},
},
{
$lookup: {
from: "col_C",
localField: "_id",
foreignField: "ret_id",
as: "task",
},
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
tasks: { $push: "$task" },
},
},
])
But like that it shows nothing. I also tried to put two groups, but than I wasn't able to have the output in only one array.
So my desired output is like that:
{
_id: id,
name: name,
tasks: [
{
_id: id,
task_name: task_name
ret_id: ret_id,
// from collection B
},
{
_id: id,
task_name: task_name
ret_id: ret_id
// from collection C
},
]
}

Facing issues in aggregation lookup. Need lookup result in same structure as defined

Schema structure
categories: [{
category: { type: Schema.Types.ObjectId, ref: 'Category' },
subCategory: [{ type: Schema.Types.ObjectId, ref: 'Category' }]
}]
Query
{
$lookup: {
from: 'categories',
localField: "categories.subCategory",
foreignField: "_id",
as: "categories.subCategory"
}
},
{
$lookup: {
from: 'categories',
localField: "categories.category",
foreignField: "_id",
as: "categories.category"
}
},
Facing issues in aggregation lookup. Need lookup result in same structure as defined.
You need to use $unwind for decoding an array, explanation is added in query comment below:
unwind your categories array
lookup for category with Category collection
unwind again because lookup will return an array and unwind will convert into object
db.collection.aggregate([
{ $unwind: "$categories" },
{
$lookup: {
from: "Category",
localField: "categories.category",
foreignField: "_id",
as: "categories.category"
}
},
{ $unwind: "$categories.category" },
lookup for subCategory with Category collection
{
$lookup: {
from: "Category",
localField: "categories.subCategory",
foreignField: "_id",
as: "categories.subCategory"
}
},
group by _id because without group lookup will multiple documents
store $$ROOT in root var, because we need to use it for replace root
push categories object that we got from lookup
{
$group: {
_id: "$_id",
root: { $mergeObjects: "$$ROOT" },
categories: { $push: "$categories" }
}
},
replace new root after merging with root and $$ROOT
{
$replaceRoot: {
newRoot: {
$mergeObjects: ["$root", "$$ROOT"]
}
}
},
remove root var because no longer needed
{
$project: { root: 0 }
}
])
For the explanation purpose i have divided in to parts, you can merge easily as all are in sequence.
Working Playground: https://mongoplayground.net/p/lKd5-MjcT1K

$lookup aggregation for nested array ids

I have Label, Conversations and Messages models...
Label contains Conversations id's... I have used $lookup which works for conversation
const conversation = await Label.aggregate([
{ $match: { _id: mongoose.Types.ObjectId("5abcb74bb59afe4310c60266") } },
{ $lookup: {
from: 'conversations',
localField: 'conversations',
foreignField: '_id',
as: "conversations"
}
}])
and gives output like
[{
_id: 5abcb74bb59afe4310c60266,
name: 'Archive',
value: 'archive',
user: 5abc93a85d3914318cc8d3d7,
conversations: [ [Object], [Object] ],
__v: 0
}]
Now I have messages inside conversations
[{
_id: 5abe07717a978c41b270b1bc,
messages: [ 5abe07717a978c41b270b1bd ],
members: [ 5abc93a85d3914318cc8d3d7, 5abc9467b7c99332f0a6813c ],
__v: 0
}]
So how do I apply $lookup for the messages fields which is inside
label -> conversations -> messasges
Thanks in advance
You will have to use $unwind on $conversations. This will cause your conversations array to explode and you will now have as much documents as conversations in your hands.
Then you should do your $lookup, and (if wanted) $group to fallback on what you had before $unwind
const conversation = await Label.aggregate([
{ $match: { _id: mongoose.Types.ObjectId("5abcb74bb59afe4310c60266") } },
{ $lookup: {
from: 'conversations',
localField: 'conversations',
foreignField: '_id',
as: "conversations"
}
},
{ $unwind: "$conversations" },
{
$lookup: {
from: 'messages',
localField: 'conversations.messages',
foreignField: '_id',
as: 'myMessages'
}
}
])

Resources