How to achieve MongoDB nested lookup inside array? - mongodb

I am doing an aggregation in Paper collection like below
const papers = await Paper.aggregate([
{
"$lookup": {
"from": "reviews",
"localField": "reviewId",
"foreignField": "_id",
"as": "review"
}
},
{ $unwind: '$review' }
]);
It returns the result that contains review object which has a reviews array like:
[
{
...
review: {
_id: 5f1638770f3a8d20f8c1beeb,
reviews: [Array],
},
...
}
]
If I make the review more clear, it is like below:
{
_id: 5f1638770f3a8d20f8c1beeb
reviews: [
{
_id: 5f164395857bcdd1d8674b69,
reviewerId: 5f15b28d534b5886c0d9eb8a
},
{
_id: 5f164395857bcdd1d8674b6a,
reviewerId: 5f1358c523dc2367c43a6311
}
]
}
In above, reviewerId inside reviews array refers to user id from "users" collection. I want to get users name, email, and address in reviews array like below:
{
reviews: [
{
_id: 5f164395857bcdd1d8674b69,
reviewerId: 5f15b28d534b5886c0d9eb8a
reviewer : {
name:"some_name",
email:"abc#example.com"
}
},
{
_id: 5f164395857bcdd1d8674b6a,
reviewerId: 5f1358c523dc2367c43a6311
reviewer : {
name:"some_name",
email:"efg#example.com"
}
}
]
}
How can I achieve it?

Hopefully, the structure of your collection almost similar as I mention below in the Mongo playground.
db.reviews.aggregate([
{
$unwind: {
path: "$reviews",
preserveNullAndEmptyArrays: false
}
},
{
$lookup: {
from: "user",
localField: "reviews.reviewerId",
foreignField: "_id",
as: "reviews.reviewer"
}
},
{
$group: {
_id: "$_id",
question: {
$first: "$question"
},
reviews: {
$addToSet: "$reviews"
}
}
}
])
Working Mongo playground

Related

MongoDB $lookup function replace whole document

I'm currently running a query that looks like this:
courses = await Enrollment.aggregate([
{
$match: {
userId: userId
}
},
{
$lookup: {
from: 'courses',
localField: 'course',
foreignField: '_id',
as: 'course'
}
},
{
$unwind: '$course'
},
{
$project: {
course: {
courseCode: true,
name: true,
officialCode: true,
}
}
}
]);
This produces results that look like this
[{
"_id": "61e0652ba5c2fe5bdcdbdc23",
"course": {
"courseCode": "code2",
"name": "Terst class 2",
"officialCode": "test 202"
}
}]
I'm wondering if there is a way for me to bring courseCode, name and officialCode to the "highest level" of the document?
Thank you in advance.
You can do it with $replaceRoot aggregation pipeline. Add this as the last step:
{
"$replaceRoot": {
"newRoot": "$course"
}
}
Working example

MongoDB aggregation, use value from one document as key in another

So I’m trying to aggregate two documents matched on an id and based on the value of the first.
Document 1
{
“id”:3
“Whats for dinner”: “dinner”,
“What is for dinner tonight”: “dinner”,
“Whats for lunch”:“lunch”
}
Document 2
{
“Id”:3
“dinner” : “We are having roast!”,
“lunch” : “We are having sandwiches”
}
I’d like to start by matching the id and test if the question exists in doc1.
then return the question from doc1 and the answer from doc 2 . Like
{“Whats for dinner”:“We are having roast!”}
I’ve tried:
{ “$match”: { “id”: 3, “Whats for dinner”:{"$exists":True}} },
{
"$lookup": {
"from": "doc 2",
"localField": "id",
"foreignField": "id",
"as": "qa"
}
}
But from here I can’t figure out how to use the value from doc1 as key in doc2
It might be simple! but I’m a new to this, and just can’t get it to work!?
Crazy data model! This would be a solution:
db.doc1.aggregate([
{ $project: { data: { $objectToArray: "$$ROOT" } } },
{ $unwind: "$data" },
{
$lookup: {
from: "doc2",
pipeline: [
{ $project: { data: { $objectToArray: "$$ROOT" } } }
],
as: "answers"
}
},
{
$set: {
answers: {
$first: {
$filter: {
input: { $first: "$answers.data" },
cond: { $eq: [ "$$this.k", "$data.v" ] }
}
}
}
}
},
{ $match: { answers: { $exists: true } } },
{
$project: {
data: [
{
k: "$data.k",
v: "$answers.v"
}
]
}
},
{ $replaceWith: { $arrayToObject: "$data" } }
])
Mongo Playground
Better don't use any user data as key names, you will always have to juggle with $objectToArray and $arrayToObject
Maybe consider this:
questions: {
guildid: 3,
text: [
"Whats for dinner",
"What is for dinner tonight",
"Whats for lunch"
],
"nospace": 1
}

Is it possible to use the result of a query as if it were a collection in mongodb?

I have the following query, Let's call it query 1:
db.getCollection('responses').aggregate([
{ $match:{ "commentId" : ObjectId("60a59174f681ac1b34fd67a1") } },
{
$lookup:{
from:"users",
localField:"userId",
foreignField: "_id",
as:"res"
}
}
])
about the result of this query I want to do another $lookup:
db.getCollection('comments').aggregate([
{ $match:{ "postId" : "60a355a4404c1617680c1a19" } },
{
$lookup:{
from:"query 1",
localField:"_id",
foreignField: "commentId",
as:"response"
}
}
])
I would like the result to be the following
[
{
comment1: {
content,
responses: [{
response1: {
content,
user: {
content,
}
},
response2: {
content,
user: {
content,
}
},
response3: {
content,
user: {
content,
}
},
}]
},
},
{
comment2: {
content,
responses: [{
response1: {
content,
user: {
content,
}
},
response2: {
content,
user: {
content,
}
},
response3: {
content,
user: {
content,
}
},
}]
},
}
]
these are the collections
users: { _id, name, username }
posts: {
_id,
userId, //user creator of the post
text,
}
comments: {_id,
postId, //id of the publication where the comments are
text,
userId, // user creator of the comment
}
responses: {
_id,
commentid, // comment id where the response is directed
userId, //id of the publication where the response are
postId, // //id of the publication where the comments are
}
what I am looking for is that in the json it has:
list of comments with a given postId, where each one contains:
the content of the comment,
a field with the userId data,
an array with all the responses that contain the commentId field that point to this comment,
and that in turn each response has the data of the user who sent it
I already solved it, I leave the solution in case it serves someone in the future
db.getCollection('comments').aggregate([
{
$match:{
"postId" : "60a355a4404c1617680c1a19",
}
},
{ "$lookup": {
"from": "responses",
"let": { "comId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$commentId", "$$comId"] }}},
{ "$lookup": {
"from": "users",
"let": { "usId": "$userId" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$_id", "$$usId"] }}}
],
"as": "responses"
}}
],
"as": "users"
}},
I already solved it, I leave the solution in case it serves someone in the future
db.getCollection('comments').aggregate([
{
$match:{
"postId" : "60a355a4404c1617680c1a19",
}
},
{ "$lookup": {
"from": "responses",
"let": { "comId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$commentId", "$$comId"] }}},
{ "$lookup": {
"from": "users",
"let": { "usId": "$userId" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$_id", "$$usId"] }}}
],
"as": "user"
}}
],
"as": "responses"
}},
])

Mongoose lookup across 3 collections using foreign key

I have found a few questions that relate to this (here and here) but I have been unable to interpret the answers in a way that I can understand how to do what I need.
I have 3 collections: Organisations, Users, and Projects. Every project belongs to one user, and every user belongs to one organisation. From the user's id, I need to return all the projects that belong to the organisation that the logged-in user belongs to.
Returning the projects from the collection that belong to the user is easy, with this query:
const projects = await Project.find({ user: req.user.id }).sort({ createdAt: -1 })
Each user has an organisation id as a foreign key, and I think I need to do something with $lookup and perhaps $unwind mongo commands, but unlike with SQL queries I really struggle to understand what's going on so I can construct queries correctly.
EDIT: Using this query
const orgProjects = User.aggregate(
[
{
$match: { _id: req.user.id }
},
{
$project: { _id: 0, org_id: 1 }
},
{
$lookup: {
from: "users",
localField: "organisation",
foreignField: Organisation._id,
as: "users_of_org"
}
},
{
$lookup: {
from: "projects",
localField: "users_of_org._id",
foreignField: "user",
as: "projects"
}
},
{
$unset: ["organisation", "users_of_org"]
},
{
$unwind: "$projects"
},
{
$replaceWith: "$projects"
}
])
Seems to almost work, returning the following:
Aggregate {
_pipeline: [
{ '$match': [Object] },
{ '$project': [Object] },
{ '$lookup': [Object] },
{ '$lookup': [Object] },
{ '$unset': [Array] },
{ '$unwind': '$projects' },
{ '$replaceWith': '$projects' }
],
_model: Model { User },
options: {}
}
assuming your documents have a schema like this, you could do an aggregation pipeline like below with 2 $lookup stages.
db.users.aggregate(
[
{
$match: { _id: "user1" }
},
{
$project: { _id: 0, org_id: 1 }
},
{
$lookup: {
from: "users",
localField: "org_id",
foreignField: "org_id",
as: "users_of_org"
}
},
{
$lookup: {
from: "projects",
localField: "users_of_org._id",
foreignField: "user_id",
as: "projects"
}
},
{
$unset: ["org_id", "users_of_org"]
},
{
$unwind: "$projects"
},
{
$replaceWith: "$projects"
}
])

$lookup when foreignField is in nested array

I have two collections :
Student
{
_id: ObjectId("657..."),
name:'abc'
},
{
_id: ObjectId("593..."),
name:'xyz'
}
Library
{
_id: ObjectId("987..."),
book_name:'book1',
issued_to: [
{
student: ObjectId("657...")
},
{
student: ObjectId("658...")
}
]
},
{
_id: ObjectId("898..."),
book_name:'book2',
issued_to: [
{
student: ObjectId("593...")
},
{
student: ObjectId("594...")
}
]
}
I want to make a Join to Student collection that exists in issued_to array of object field in Library collection.
I would like to make a query to student collection to get the student data as well as in library collection, that will check in issued_to array if the student exists or not if exists then get the library document otherwise not.
I have tried $lookup of mongo 3.6 but I didn`t succeed.
db.student.aggregate([{$match:{_id: ObjectId("593...")}}, $lookup: {from: 'library', let: {stu_id:'$_id'}, pipeline:[$match:{$expr: {$and:[{"$hotlist.clientEngagement": "$$stu_id"]}}]}])
But it thorws error please help me in regard of this. I also looked at other questions asked at stackoverflow like. question on stackoverflow,
question2 on stackoverflow but these are comapring simple fields not array of objects. please help me
I am not sure I understand your question entirely but this should help you:
db.student.aggregate([{
$match: { _id: ObjectId("657...") }
}, {
$lookup: {
from: 'library',
localField: '_id' ,
foreignField: 'issued_to.student',
as: 'result'
}
}])
If you want to only get the all book_names for each student you can do this:
db.student.aggregate([{
$match: { _id: ObjectId("657657657657657657657657") }
}, {
$lookup: {
from: 'library',
let: { 'stu_id': '$_id' },
pipeline: [{
$unwind: '$issued_to' // $expr cannot digest arrays so we need to unwind which hurts performance...
}, {
$match: { $expr: { $eq: [ '$issued_to.student', '$$stu_id' ] } }
}, {
$project: { _id: 0, "book_name": 1 } // only include the book_name field
}],
as: 'result'
}
}])
This might not be a very good answer, but if you can change your schema of Library to:
{
_id: ObjectId("987..."),
book_name:'book1'
issued_to: [
ObjectId("657..."),
ObjectId("658...")
]
},
{
_id: "ObjectId("898...")",
book_name:'book2'
issued_to: [
ObjectId("593...")
ObjectId("594...")
]
}
Then when you do:
{
$lookup: {
from: 'student',
localField: 'issued_to',
foreignField: '_id',
as: 'issued_to_students', // this creates a new field without overwriting your original 'issued_to'
}
},
You should get, based on your example above:
{
_id: ObjectId("987..."),
book_name:'book1'
issued_to_students: [
{ _id: ObjectId("657..."), name: 'abc', ... },
{ _id: ObjectId("658..."), name: <name of this _id>, ... }
]
},
{
_id: "ObjectId("898...")",
book_name:'book2'
issued_to: [
{ _id: ObjectId("593..."), name: 'xyz', ... },
{ _id: ObjectId("594..."), name: <name of this _id>, ... }
]
}
You need to $unwind the issued_to from library collection to match the issued_to.student with _id
db.student.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(id) } },
{ "$lookup": {
"from": Library.collection.name,
"let": { "studentId": "$_id" },
"pipeline": [
{ "$unwind": "$issued_to" },
{ "$match": { "$expr": { "$eq": [ "$issued_to.student", "$$studentId" ] } } }
],
"as": "issued_to"
}}
])