Mongodb $lookup in data aggregation - localField attribute character issue - mongodb

I was trying to join multiple collections in MongoDB using the aggregate $lookup.The issue I am facing right now $lookup localField attribute does not accept $ character.
I have following three collections.
student
{
"_id" : ObjectId("5db12e6dfc368dff1cfc30e5"),
"studentId" : "S97YAREA51",
"name" : "Has Maara duwa",
"age" : 22
}
course
{
"_id" : ObjectId("5db12e6dfc368dff1cfc20e5"),
"courseId" : "C04865690",
"courseName" : "Love and Empathy"
}
studentCourse
{
"_id" : ObjectId("5db12e6dfc368dff1cfc10e5"),
"student" : {
"$ref" : "student",
"$id" : ObjectId("5db12e6dfc368dff1cfc30e5")
},
"course" : {
"$ref" : "course",
"$id" : ObjectId("5db12e6dfc368dff1cfc20e5")
}
}
I need to update studentCourse collection records to something like below.
{
"_id" : ObjectId("5db12e6dfc368dff1cfc10e5"),
"student" : {
"$ref" : "student",
"$id" : ObjectId("5db12e6dfc368dff1cfc30e5")
},
"course" : {
"$ref" : "course",
"$id" : ObjectId("5db12e6dfc368dff1cfc20e5")
},
"studentId" : "S97YAREA51",
"courseId" : "C04865690"
}
I tried to create the following aggregation query, but it was failing due to localField does not accept $ character. I would be much appreciated if somebody provides me a simple solution for this matter.
db.studentCourse.aggregate([
{
$lookup:{
from: "student",
localField: "student.$id",
foreignField: "_id",
as: "student"
}
},
{ $unwind:"$student" }, // $unwind used for getting data in object or for one record only
{
$lookup:{
from: "course",
localField: "course.$id",
foreignField: "_id",
as: "course"
}
},
{ $unwind:"$course" },
// define which fields are you want to fetch
{
$project:{
"_id" : 1,
"student" : 1,
"course" : 1,
"studentId" : "$student.studentId",
"courseId" : "$course.courseId",
}
}
]);

Related

MongoDB Aggregation - How can I" $lookup" a nested document "_id"?

I successfully thanks to the help of the people here managed to $lookup two IDs in my document with their representive document in another collection. The next step I need to take is to further lookup a "nested" ID (refering to a document in another collection).
I tried to simply put another $lookup pipeline up but that just worked part-wise.
So it happens that an "empty" document was included into the chieftain attributes and all other attributes of chieftain where somewhat removed.
See my current aggregate:
db.getCollection('village').aggregate([
{
"$match": { _id: "111" }
},
{
"$lookup": {
from: "character",
localField: "chieftainId",
foreignField: "_id",
as: "chieftain"
}
},
{
"$lookup": {
from: "character",
localField: "villagerIds",
foreignField: "_id",
as: "villagers"
}
},
{
"$lookup": {
from: "bloodline",
localField: "chieftain.bloodline",
foreignField: "_id",
as: "chieftain.bloodline"
}
},
{ "$project" : { "villagerIds" : 0, "chieftainId" : 0}},
{ "$unwind" : "$chieftain" }
])
The result of that is the following:
{
"_id" : "111",
"name" : "MyVillage",
"reputation" : 0,
"chieftain" : {
"bloodline" : []
},
"villagers" : [
{
"_id" : "333",
"name" : "Bortan",
"age" : 21,
"bloodlineId" : "7f02191f-90af-406e-87ff-41d5b4387999",
"villageId" : "foovillage",
"professionId" : "02cbb10a-6c0f-4249-a932-3f40e12d32c5"
},
{
"_id" : "444",
"name" : "Blendi",
"age" : 21,
"bloodlineId" : "b3a8ffeb-27aa-4e2e-a8e6-b382554f326a",
"villageId" : "foovillage",
"professionId" : "45dc9350-c84a-491d-a49a-524834dd5773"
}
]
}
I expected the chieftain part to look like this (this is how the chieftain document looks like without the 'last' $lookup I added):
"chieftain" : {
"_id" : "222",
"name" : "Bolzan",
"age" : 21,
"bloodlineId" : "7c2926f9-2f20-4ccf-846a-c9966970fa9b", // this should be resolved/lookedup
"villageId" : "foovillage",
},
At the point of the lookup, chieftan is an array, so setting the chieftan.bloodline replaces the array with an object containing only the bloodline field.
Move the { "$unwind" : "$chieftain" } stage to before the bloodline lookup stage so the lookup is dealing with an object.

How to use $lookup more than once in against the same collection? [duplicate]

I want to join more than two collections in MongoDB using the aggregate $lookup. Is it possible to join? Give me some examples.
Here I have three collections:
users:
{
"_id" : ObjectId("5684f3c454b1fd6926c324fd"),
"email" : "admin#gmail.com",
"userId" : "AD",
"userName" : "admin"
}
userinfo:
{
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"phone" : "0000000000"
}
userrole:
{
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"role" : "admin"
}
The join feature supported by Mongodb 3.2 and later versions. You can use joins by using aggregate query.
You can do it using below example :
db.users.aggregate([
// Join with user_info table
{
$lookup:{
from: "userinfo", // other table name
localField: "userId", // name of users table field
foreignField: "userId", // name of userinfo table field
as: "user_info" // alias for userinfo table
}
},
{ $unwind:"$user_info" }, // $unwind used for getting data in object or for one record only
// Join with user_role table
{
$lookup:{
from: "userrole",
localField: "userId",
foreignField: "userId",
as: "user_role"
}
},
{ $unwind:"$user_role" },
// define some conditions here
{
$match:{
$and:[{"userName" : "admin"}]
}
},
// define which fields are you want to fetch
{
$project:{
_id : 1,
email : 1,
userName : 1,
userPhone : "$user_info.phone",
role : "$user_role.role",
}
}
]);
This will give result like this:
{
"_id" : ObjectId("5684f3c454b1fd6926c324fd"),
"email" : "admin#gmail.com",
"userName" : "admin",
"userPhone" : "0000000000",
"role" : "admin"
}
Hope this will help you or someone else.
Thanks
You can actually chain multiple $lookup stages. Based on the names of the collections shared by profesor79, you can do this :
db.sivaUserInfo.aggregate([
{
$lookup: {
from: "sivaUserRole",
localField: "userId",
foreignField: "userId",
as: "userRole"
}
},
{
$unwind: "$userRole"
},
{
$lookup: {
from: "sivaUserInfo",
localField: "userId",
foreignField: "userId",
as: "userInfo"
}
},
{
$unwind: "$userInfo"
}
])
This will return the following structure :
{
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"phone" : "0000000000",
"userRole" : {
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"role" : "admin"
},
"userInfo" : {
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"phone" : "0000000000"
}
}
Maybe this could be considered an anti-pattern because MongoDB wasn't meant to be relational but it is useful.
According to the documentation, $lookup can join only one external collection.
What you could do is to combine userInfo and userRole in one collection, as provided example is based on relational DB schema. Mongo is noSQL database - and this require different approach for document management.
Please find below 2-step query, which combines userInfo with userRole - creating new temporary collection used in last query to display combined data.
In last query there is an option to use $out and create new collection with merged data for later use.
create collections
db.sivaUser.insert(
{
"_id" : ObjectId("5684f3c454b1fd6926c324fd"),
"email" : "admin#gmail.com",
"userId" : "AD",
"userName" : "admin"
})
//"userinfo"
db.sivaUserInfo.insert(
{
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"phone" : "0000000000"
})
//"userrole"
db.sivaUserRole.insert(
{
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"role" : "admin"
})
"join" them all :-)
db.sivaUserInfo.aggregate([
{$lookup:
{
from: "sivaUserRole",
localField: "userId",
foreignField: "userId",
as: "userRole"
}
},
{
$unwind:"$userRole"
},
{
$project:{
"_id":1,
"userId" : 1,
"phone" : 1,
"role" :"$userRole.role"
}
},
{
$out:"sivaUserTmp"
}
])
db.sivaUserTmp.aggregate([
{$lookup:
{
from: "sivaUser",
localField: "userId",
foreignField: "userId",
as: "user"
}
},
{
$unwind:"$user"
},
{
$project:{
"_id":1,
"userId" : 1,
"phone" : 1,
"role" :1,
"email" : "$user.email",
"userName" : "$user.userName"
}
}
])
First add the collections and then apply lookup on these collections. Don't use $unwind
as unwind will simply separate all the documents of each collections. So apply simple lookup and then use $project for projection.
Here is mongoDB query:
db.userInfo.aggregate([
{
$lookup: {
from: "userRole",
localField: "userId",
foreignField: "userId",
as: "userRole"
}
},
{
$lookup: {
from: "userInfo",
localField: "userId",
foreignField: "userId",
as: "userInfo"
}
},
{$project: {
"_id":0,
"userRole._id":0,
"userInfo._id":0
}
} ])
Here is the output:
/* 1 */ {
"userId" : "AD",
"phone" : "0000000000",
"userRole" : [
{
"userId" : "AD",
"role" : "admin"
}
],
"userInfo" : [
{
"userId" : "AD",
"phone" : "0000000000"
}
] }
Thanks.
first lookup finds all the products where p.cid = categories._id,simlarly 2nd lookup
finds all products where p.sid = subcategories._id.
let dataQuery :any = await ProductModel.aggregate([ { $lookup:{
from :"categories",
localField:"cid",
foreignField :"_id",
as :"products"
}
},
{
$unwind: "$products"
},
{ $lookup:{
from :"subcategories",
localField:"sid",
foreignField :"_id",
as :"productList"
}
},
{
$unwind: "$productList"
},
{
$project:{
productList:0
}
}
]);

Join Multiple Tables MongoDB and returned merged result

I have two collections one is user and other is address.
User document has list which contains the publicId of addresses.
I am writing an API to fetch users. How can I return the merged result of user and addresses documents?
User :
{
"_id" : ObjectId("5684f3c454b1fd6926c324fd"),
"email" : "user#example.com",
"userId" : "userId",
"addresses" : ["A1234","A5678"]
}
Address :
{
"_id" : ObjectId("56d82612b63f1c31cf906004"),
"publicId" : "A1234"
"addressLine1" : "AD1",
"addressLine2" : "AD1",
"pin" : "001"
}
{
"_id" : ObjectId("56d82612b63f1c31cf906005"),
"publicId" : "A5678"
"addressLine1" : "AD2",
"addressLine2" : "AD2",
"pin" : "002"
}
What I am trying to achieve is following
{
"_id" : ObjectId("5684f3c454b1fd6926c324fd"),
"email" : "user#example.com",
"userId" : "userId",
"addresses" : [
{
"publicId" : "A1234"
"addressLine1" : "AD1",
"addressLine2" : "AD1",
"pin" : "001"
},
{
"publicId" : "A5678"
"addressLine1" : "AD2",
"addressLine2" : "AD2",
"pin" : "002"
}
]
}
how can I achieve this using aggregate query?
for that you can use $lookup stage from aggregation framework:
db.user.aggregate([
{$lookup:
{
from : 'addresses',
localField: 'Addresses',
foreignField: 'publicId',
as: 'Addresses'
}
},
{$project: {'Addresses._id': 0} }
]).pretty()
for using $lookup (similar to left outer join) you need to use MongoDB version 3.2 or above.
I got it working. Following is the aggregate query I used.
db.user.aggregate([
{
$unwind: "$addresses"
},
{
$lookup:
{
from: "address",
localField: "addresses",
foreignField: "publicId",
as: "addresses"
}
},
{
$match: {
"userId":"userId",
"addresses": { $ne: [] }
}
}
])

using match for both linked collection of mongo in lookup

I have two collections - orders, feedback
Orders:
{
"restaurantId" : NumberInt(138),
"referenceNo" : "1980DBF5",
"orderId" : "1045593",
"userId" : NumberInt(6664960),
"userEmail" : "user#g.com",
"firstName" : "User"
}
Feedbacks:
{
"rating" : NumberInt(4),
"additionalComments" : "it is working",
"referenceNo" : "7D02097F",
"productId" : NumberInt(1),
"restaurantId" : NumberInt(138),
"orderType" : "1"
}
I need to make a join on these two collections.
Also, there will be filter inputs on rating, referenceNo, userEmail, firstName.
How can I use match to get data from two collections?
This is my aggregate query:
[
{
"$lookup":
{
"from": "orders",
"localField": "referenceNo",
"foreignField": "referenceNo",
"as": "data"
}
}
]
I am using this query on feedback model.
The data that I get is:
{
"rating" : NumberInt(4),
"additionalComments" : "it is working",
"referenceNo" : "7D02097F",
"productId" : NumberInt(1),
"restaurantId" : NumberInt(138),
"orderType" : "1"
"data": [{
"restaurantId" : NumberInt(138),
"referenceNo" : "1980DBF5",
"orderId" : "1045593",
"userId" : NumberInt(6664960),
"userEmail" : "user#g.com",
"firstName" : "User"
}]
}
Also, would it be possible to add limits here?
Earlier I was using this on orders -
mQuery.skip(Number(paginate.offset)).limit(Number(paginate.limit));
Check if this helps....
db.feedbacks.aggregate([ {
$lookup:
{
from: "users",
localField: "whatever",
foreignField: "whatever",
as: "data"
}},
{
$unwind: "$data"
},
{
$match:
{
"data.orderId":"1045593"} //conditions
},
{
$limit:1 //limit
}
]);

How to join multiple collections with $lookup in mongodb

I want to join more than two collections in MongoDB using the aggregate $lookup. Is it possible to join? Give me some examples.
Here I have three collections:
users:
{
"_id" : ObjectId("5684f3c454b1fd6926c324fd"),
"email" : "admin#gmail.com",
"userId" : "AD",
"userName" : "admin"
}
userinfo:
{
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"phone" : "0000000000"
}
userrole:
{
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"role" : "admin"
}
The join feature supported by Mongodb 3.2 and later versions. You can use joins by using aggregate query.
You can do it using below example :
db.users.aggregate([
// Join with user_info table
{
$lookup:{
from: "userinfo", // other table name
localField: "userId", // name of users table field
foreignField: "userId", // name of userinfo table field
as: "user_info" // alias for userinfo table
}
},
{ $unwind:"$user_info" }, // $unwind used for getting data in object or for one record only
// Join with user_role table
{
$lookup:{
from: "userrole",
localField: "userId",
foreignField: "userId",
as: "user_role"
}
},
{ $unwind:"$user_role" },
// define some conditions here
{
$match:{
$and:[{"userName" : "admin"}]
}
},
// define which fields are you want to fetch
{
$project:{
_id : 1,
email : 1,
userName : 1,
userPhone : "$user_info.phone",
role : "$user_role.role",
}
}
]);
This will give result like this:
{
"_id" : ObjectId("5684f3c454b1fd6926c324fd"),
"email" : "admin#gmail.com",
"userName" : "admin",
"userPhone" : "0000000000",
"role" : "admin"
}
Hope this will help you or someone else.
Thanks
You can actually chain multiple $lookup stages. Based on the names of the collections shared by profesor79, you can do this :
db.sivaUserInfo.aggregate([
{
$lookup: {
from: "sivaUserRole",
localField: "userId",
foreignField: "userId",
as: "userRole"
}
},
{
$unwind: "$userRole"
},
{
$lookup: {
from: "sivaUserInfo",
localField: "userId",
foreignField: "userId",
as: "userInfo"
}
},
{
$unwind: "$userInfo"
}
])
This will return the following structure :
{
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"phone" : "0000000000",
"userRole" : {
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"role" : "admin"
},
"userInfo" : {
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"phone" : "0000000000"
}
}
Maybe this could be considered an anti-pattern because MongoDB wasn't meant to be relational but it is useful.
According to the documentation, $lookup can join only one external collection.
What you could do is to combine userInfo and userRole in one collection, as provided example is based on relational DB schema. Mongo is noSQL database - and this require different approach for document management.
Please find below 2-step query, which combines userInfo with userRole - creating new temporary collection used in last query to display combined data.
In last query there is an option to use $out and create new collection with merged data for later use.
create collections
db.sivaUser.insert(
{
"_id" : ObjectId("5684f3c454b1fd6926c324fd"),
"email" : "admin#gmail.com",
"userId" : "AD",
"userName" : "admin"
})
//"userinfo"
db.sivaUserInfo.insert(
{
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"phone" : "0000000000"
})
//"userrole"
db.sivaUserRole.insert(
{
"_id" : ObjectId("56d82612b63f1c31cf906003"),
"userId" : "AD",
"role" : "admin"
})
"join" them all :-)
db.sivaUserInfo.aggregate([
{$lookup:
{
from: "sivaUserRole",
localField: "userId",
foreignField: "userId",
as: "userRole"
}
},
{
$unwind:"$userRole"
},
{
$project:{
"_id":1,
"userId" : 1,
"phone" : 1,
"role" :"$userRole.role"
}
},
{
$out:"sivaUserTmp"
}
])
db.sivaUserTmp.aggregate([
{$lookup:
{
from: "sivaUser",
localField: "userId",
foreignField: "userId",
as: "user"
}
},
{
$unwind:"$user"
},
{
$project:{
"_id":1,
"userId" : 1,
"phone" : 1,
"role" :1,
"email" : "$user.email",
"userName" : "$user.userName"
}
}
])
First add the collections and then apply lookup on these collections. Don't use $unwind
as unwind will simply separate all the documents of each collections. So apply simple lookup and then use $project for projection.
Here is mongoDB query:
db.userInfo.aggregate([
{
$lookup: {
from: "userRole",
localField: "userId",
foreignField: "userId",
as: "userRole"
}
},
{
$lookup: {
from: "userInfo",
localField: "userId",
foreignField: "userId",
as: "userInfo"
}
},
{$project: {
"_id":0,
"userRole._id":0,
"userInfo._id":0
}
} ])
Here is the output:
/* 1 */ {
"userId" : "AD",
"phone" : "0000000000",
"userRole" : [
{
"userId" : "AD",
"role" : "admin"
}
],
"userInfo" : [
{
"userId" : "AD",
"phone" : "0000000000"
}
] }
Thanks.
first lookup finds all the products where p.cid = categories._id,simlarly 2nd lookup
finds all products where p.sid = subcategories._id.
let dataQuery :any = await ProductModel.aggregate([ { $lookup:{
from :"categories",
localField:"cid",
foreignField :"_id",
as :"products"
}
},
{
$unwind: "$products"
},
{ $lookup:{
from :"subcategories",
localField:"sid",
foreignField :"_id",
as :"productList"
}
},
{
$unwind: "$productList"
},
{
$project:{
productList:0
}
}
]);