Mongo multiple $lookup in any collections - mongodb

I'm trying to set up a query on the mongo that searches in 3 different collections.
Document of client:
{
"_id": ObjectId("1a")
"razaosocial:"32423424",
"prepository": [
{
"$ref": "prepository",
"$id": ObjectId("2a")
}
]
}
Document of prepository:
{
"_id": ObjectId("2a")
"name:"Jonh",
"prepository": {
"$ref": "representative",
"$id": ObjectId("3a")
}
}
Document of representative:
{
"_id": ObjectId("3a")
"name:"Josh"
}
I'm doing it this way, but it doesn't return anything:
db.clients.aggregate(
[
{
$lookup: {
from: 'prepository',
localField: 'prepository',
foreignField: 'id',
as: 'prepository'
}
},
{ $unwind: "$prepository" },
{ $lookup: {
from: 'representative',
localField: 'id',
foreignField: 'prepository._id',
as: 'prepository.repre'
}
},
{ $group: {
_id: "$_id",
client: { $first: "$razaosocial" },
users: {
$push: "$prepository"
}
} }
])
I'm trying to return
{
"_id": "1a"
"razaosocial:"32423424",
"prepository": [
{
"_id": "2a"
"name:"Jonh",
"representative": {
"_id": "3a"
"name:"Josh"
}
}
]
}
I am grateful for any help

You can use nested lookup,
$lookup with prepository collection
$match with prepository's id
$lookup with representative collection
$unwind deconstruct representative array
db.client.aggregate([
{
$lookup: {
from: "prepository",
as: "prepository",
let: { prepository: "$prepository" },
pipeline: [
{ $match: { $expr: { $in: ["$_id", "$$prepository"] } } },
{
$lookup: {
from: "representative",
localField: "representative",
foreignField: "_id",
as: "representative"
}
},
{ $unwind: "$representative" }
]
}
}
])
Playground

Related

Getting Invalid Pipline Operator in MongoDB intellishell studio 3t

I am running the following query, trying to append a new element to an existing array, but it keeps giving me invalid pipeline operator whenever i user $push or $addToSet.
What can i do to make the new field i am adding add the new element?
What am I doing wrong?
Here is my code:
db.getCollection("Branch").aggregate([{
$match: {
"company.id": "439424454545436160"
}
},
{
$lookup: {
from: "Desk",
localField: "_id",
foreignField: "branch.id",
as: "desks"
}
},
{
$unwind: "$desks"
},
{
$lookup: {
from: "Service",
localField: "_id",
foreignField: "branch.id",
as: "services"
}
},
{
$addFields: {
"desk-services": {
$map: {
input: "$services._id",
as: "sv",
in: {
"$cond": {
if: {
"$eq": ["$desks.services.id", "$$sv._id" ]
},
then: {
$addToSet: "$$sv"
},
else: {
}
}
}
}
}
}
},
{
$project: {
"_id": 1,
"name": "$desks.name",
"label": "$desks.label",
"services": "$desks.services",
"branch": "$name",
"active": 1,
}
},
{
$sort: {
"branch": 1,
"desk": 1,
}
}
])
UPDATE:
output without mapping:
Expected output:
i want the below output but with a new element that is in the service collection

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

how to push data into array mongodb

Lookup with three collections re-manage data. I got confuse which to use. Please guide on this
db.post.aggregate([
{
$lookup: {
from: "users",
localField: "created_by",
foreignField: "_id",
as: "created_users"
}
},
{
$lookup: {
from: "comments",
let: {
p_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$post_id",
"$$p_id"
]
}
}
}
],
as: "comments"
}
},
{
$lookup: {
from: "users",
localField: "comments.sender_id",
foreignField: "_id",
as: "commented_user"
}
}
])
There are three collection posts, user, and comments we would like to get who has comment for a post, Commented user should come under comments like this
[
{
"_id": ObjectId("5eeb02881982961ada625c7d"),
"commented_user": [
{
"_id": ObjectId("5e4d0973babf2b74ca868f4d"),
"first_name": "James",
"last_name": "Smith",
"timestamp": 1.582106995137e+12
},
{
"_id": ObjectId("5e4d0973babf2b74ca868f6d"),
"first_name": "Alex",
"last_name": "Jimy",
"timestamp": 1.582106995139e+12
}
],
"comments": [
{
"_id": ObjectId("5eeb08e26fb7f270e4077617"),
"date": 1.592461538924e+12,
"post_id": ObjectId("5eeb02881982961ada625c7d"),
"sender_id": ObjectId("5e4d0973babf2b74ca868f4d"),
"text": "Nice ",
"commented_user": {
"_id": ObjectId("5e4d0973babf2b74ca868f4d"),
"first_name": "James",
"last_name": "Smith",
"timestamp": 1.582106995137e+12
},
},
{
"_id": ObjectId("5eeb08e26fb7f270e4077618"),
"date": 1.592461538923e+12,
"post_id": ObjectId("5eeb02881982961ada625c7d"),
"sender_id": ObjectId("5e4d0973babf2b74ca868f6d"),
"text": "Nice One",
"commented_user": {
"_id": ObjectId("5e4d0973babf2b74ca868f6d"),
"first_name": "Alex",
"last_name": "Jimy",
"timestamp": 1.582106995137e+12
},
}
],
"created_by": ObjectId("5e4e74eb380054797d9db623"),
"created_users": [],
"date": 1.589441206774e+12,
"title": "Covid19"
}
]
here is my attempt https://mongoplayground.net/p/UwRjj-er0K5
Please help on this thanks
You can do something like this:
The strategy is $unwinding the results so we can match per comment and then reconstruct the former structure.
db.post.aggregate([
{
$lookup: {
from: "users",
localField: "created_by",
foreignField: "_id",
as: "created_users"
}
},
{
$lookup: {
from: "comments",
let: {
p_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$post_id",
"$$p_id"
]
}
}
}
],
as: "comments"
}
},
{
$unwind: "$comments"
},
{
$lookup: {
from: "users",
localField: "comments.sender_id",
foreignField: "_id",
as: "commented_user"
}
},
{
$unwind: "$commented_user"
},
{
$addFields: {
comments: {
$mergeObjects: [
"$comments",
{
commented_user: "$commented_user"
}
]
}
}
},
{
$group: {
_id: "$_id",
comments: {
$push: "$comments"
},
created_by: {
$first: "$created_by"
},
created_users: {
$first: "$created_users"
},
date: {
$first: "$date"
},
title: {
$first: "$title"
},
}
}
])
Mongo Playground
Starting with your the code you have attempted, you can just move your second $lookup into the first $lookup's pipeline, then $unwind it
db.post.aggregate([
{
$lookup: {
from: "users",
localField: "created_by",
foreignField: "_id",
as: "created_users"
}
},
{
$lookup: {
from: "comments",
let: { p_id: "$_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$post_id", "$$p_id"] }
}
},
{ // second level $lookup
$lookup: {
from: "users",
localField: "sender_id",
foreignField: "_id",
as: "commented_user"
}
},
{ // $unwind to get single object instead of array
$unwind: "$commented_user"
}
],
as: "comments"
}
},
])
Mongo Playground
Note for OP: There is discussion to get the latest comment instead. With this approach you can $sort and $limit in the outermost $lookup
db.post.aggregate([
{
$lookup: {
from: "users",
localField: "created_by",
foreignField: "_id",
as: "created_users"
}
},
{
$lookup: {
from: "comments",
let: { p_id: "$_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$post_id", "$$p_id"] }
}
},
{
$sort: { date: -1 } // or { _id: -1 }
},
{
$limit: 1 // limit to only one element
},
{
$lookup: {
from: "users",
localField: "sender_id",
foreignField: "_id",
as: "commented_user"
}
},
{
$unwind: "$commented_user"
}
],
as: "comments"
}
},
{ // // $unwind to get single object instead of array
$unwind: "$comments"
}
])
Mongo Playground

How to perform lookup in aggregation when foreignField is in array?

I have two collections:
// users
{
_id: "5cc7c8773861275845167f7a",
name: "John",
accounts: [
{
"_id": "5cc7c8773861275845167f76",
"name": "Name1",
},
{
"_id": "5cc7c8773861275845167f77",
"name": "Name2",
}
]
}
// transactions
{
"_id": "5cc7c8773861275845167f75",
"_account": "5cc7c8773861275845167f76",
}
Using lookup I want to populate _account field in transactions collection with respective element from users.accounts array.
So, I want the final result as:
{
"_id": "5cc7c8773861275845167f75",
"_account": {
"_id": "5cc7c8773861275845167f76",
"name": "Name1",
},
}
I have already tried using this code:
db.transactions.aggregate([
{
$lookup:
{
from: "users.accounts",
localField: "_account",
foreignField: "_id",
as: "account"
}
}
])
In the result account array comes as empty.
What is the correct way to do it ?
You can use below aggregation with mongodb 3.6 and above
db.transactions.aggregate([
{ "$lookup": {
"from": "users",
"let": { "account": "$_account" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$$account", "$accounts._id"] } } },
{ "$unwind": "$accounts" },
{ "$match": { "$expr": { "$eq": ["$$account", "$accounts._id"] } } }
],
"as": "_account"
}},
{ '$unwind': '$_account' }
])
Try with this
I think case 1 is better.
1)-
db.getCollection('transactions').aggregate([
{
$lookup:{
from:"user",
localField:"_account",
foreignField:"accounts._id",
as:"trans"
}
},
{
$unwind:{
path:"$trans",
preserveNullAndEmptyArrays:true
}
},
{
$unwind:{
path:"$trans.accounts",
preserveNullAndEmptyArrays:true
}
},
{$match: {$expr: {$eq: ["$trans.accounts._id", "$_account"]}}},
{$project:{
_id:"$_id",
_account:"$trans.accounts"
}}
])
2)-
db.getCollection('users').aggregate([
{
$unwind:{
path:"$accounts",
preserveNullAndEmptyArrays:true
}
},
{
$lookup:
{
from: "transactions",
localField: "accounts._id",
foreignField: "_account",
as: "trans"
}
},
{$unwind:"$trans"},
{
$project:{
_id:"$trans._id",
_account:"$accounts"
}
}
])

Remove field from embedded projection document after lookup

I have the following mongo aggregate query:
return db.collection('projects').aggregate([
{
$match: {
agents: ObjectId(agent)
}
},
{
$lookup: {
from: "agents",
localField: "agents",
foreignField: "_id",
as: "agents"
}
},
{
$lookup: {
from: "roles",
localField: "roles",
foreignField: "_id",
as: "roles"
}
},
{
$lookup: {
from: "agencies",
localField: "agency",
foreignField: "_id",
as: "agency"
}
},
{
$lookup: {
from: "resources",
localField: "roles.applicants",
foreignField: "_id",
as: "roles.applicants"
}
}
])
It works as it should, embedding the proper documents. However, the "password_hash" field is showing for each applicant. I want to remove that field. If I try to project and set roles.applicants.password_hash: 0 I actually end up getting the entire applicant without the password hash, but the rest of the roles fields are no longer there. So I get something that looks like:
roles: {
applicants: {
name: "Josh"
}
}
That should be
roles: {
title: "Super Hero",
applicants: {
name: "Josh"
}
}
I figured it out. Here is how I did it. First, the projection wasn't the issue with why the roles document was missing fields. It was the lookup.
What I did was changed the roles lookup to use the pipeline method on the nested documents and then did another pipeline on applicants, which first matched the applicants from the resource collection and then handled the projection. It looks like this.
{
$lookup: {
from: "roles",
let: { "roles": "$roles" },
pipeline: [
{ $match: { $expr: { $in: [ "$_id", "$$roles" ] } } },
{
$lookup: {
from: "resources",
let: { "applicants": "$applicants" },
pipeline: [
{ $match: { $expr: { $in: [ "$_id", "$$applicants" ] } } },
{
$project: {
first_name: 1
}
}
],
as: "applicants"
}
}
],
as: "roles"
}
}
The entire aggregate looks like this
return db.collection('projects').aggregate([
{
$match: {
agents: ObjectId(agent)
}
},
{
$lookup: {
from: "agents",
localField: "agents",
foreignField: "_id",
as: "agents"
}
},
{
$lookup: {
from: "agencies",
localField: "agency",
foreignField: "_id",
as: "agency"
}
},
{
$lookup: {
from: "roles",
let: { "roles": "$roles" },
pipeline: [
{ $match: { $expr: { $in: [ "$_id", "$$roles" ] } } },
{
$lookup: {
from: "resources",
let: { "applicants": "$applicants" },
pipeline: [
{ $match: { $expr: { $in: [ "$_id", "$$applicants" ] } } },
{
$project: {
first_name: 1
}
}
],
as: "applicants"
}
}
],
as: "roles"
}
}
])