MongoDB $lookup in an array of object with different value - node.js

I've got two MongoDB collections and i want to replace all the ID by image object.
Datset collection :
{
_id: 5d888b3a29cf9a5e93d80756,
date: 2019-09-23T11:33:00.000Z,
robot: { _id: 5d9c7caf0ad3ea493210527b, name: 'erx01' },
images:[
{
rgb: 5da57db7cacd6e00c2a0f677,
ir: 5da57db7cacd6e00c2a0f678,
lbl: 5da57db7cacd6e00c2a0f676
},
{
rgb: 5da5808f65f3440032edee78,
ir: 5da5808f65f3440032edee79,
lbl: 5da5808f65f3440032edee77
}
]
}
Image collection :
{
_id: 5da57db7cacd6e00c2a0f677,
filename: 'iI7gVXyf31b1wedzBXD.png',
metadata: [Object],
type: 'RGB'
}
That's what i tried and got as a result :
{
$unwind: "$images"
},
{
$lookup: {
from: "image",
localField: "images.rgb",
foreignField: "_id",
as: "images.rgb"
}
},
{
$lookup: {
from: "image",
localField: "images.lbl",
foreignField: "_id",
as: "images.lbl"
}
},
{
$lookup: {
from: "image",
localField: "images.ir",
foreignField: "_id",
as: "images.ir"
}
},
Result :
images: {
rgb: [ [Object] ],
ir: [ [Object] ],
lbl: [ [Object] ]
}
What i want :
images : [
{
rgb: [Object],
ir: [Object] ,
lbl: [Object]
}
{ ... }
]
It's half working because i got the right infos but not as a an array. i don't want an array of RGB / IR and LBL image but an array of object containing one single RGB/IR/LBL image.
i tried to use unwind too but i got nothing revelant.

Well you did half the things correct you just need to add group and project to change the output to your desired format
{
$unwind: "$images"
},
{
$lookup: {
from: "image",
localField: "images.rgb",
foreignField: "_id",
as: "images.rgb"
}
},
{
$unwind: {
"path": "$images.rgb",
"preserveNullAndEmptyArrays": true
}
},
{
$lookup: {
from: "image",
localField: "images.lbl",
foreignField: "_id",
as: "images.lbl"
}
},
{
$unwind: {
"path": "$images.lbl",
"preserveNullAndEmptyArrays": true
}
},
{
$lookup: {
from: "image",
localField: "images.ir",
foreignField: "_id",
as: "images.ir"
}
},
{
$unwind: {
"path": "$images.ir",
"preserveNullAndEmptyArrays": true
}
},
{
$group: {
_id: {
id: '$_id',
},
date: { $last: '$date' },
robot: { $last: '$robot' },
images: { $push: '$images' }
}
},
{
$project: {
_id: '$_id.id',
date: 1,
robot: 1,
images: 1
}
}
Notice the $unwind after every lookup it is needed so that the output of lookup is not in array.

Related

retrieve nested data from mongodb record depend on condition

I have a board record like this
{
_id:"6257d2edfbc4d4d1d53bb7b8"
tasks:["624acd4fce5fbb5ce20ddb88","624acd68ce5fbb5ce20de3b5"]
}
This is my tasks collection
[
{
_id:"624acd4fce5fbb5ce20ddb88",
status:"inProgress"
},
{
_id:"624acd68ce5fbb5ce20de3b5"
status:"done"
}
]
I am trying to retrieve the board data and the count of inProgress tasks and done tasks in aggregate like this but it show me this error
input to $filter must be an array not object
Board.aggregate([
{
$facet: {
totalDocs: [{ $match: { $and: [data] } }],
totalInProgress: [
{
$lookup: {
from: "tasks",
localField: "tasks",
foreignField: "_id",
as: "tasks",
},
},
{ $unwind: { path: "$tasks", preserveNullAndEmptyArrays: true } },
{
$project: {
tasks: {
$filter: {
input: "$tasks",
as: "task",
cond: { $eq: ["$$task.status", "inProgress"] },
},
},
},
},
],
},
},
]);

lookup with add extra field in mongodb

My OBJ
[{
_id:XXXXXXXXXX,
role:admin
},
{
_id:XXXXXXXXXX,
role:superUser
}]
and need results using aggregation how to solve this using aggregation
[{
name:'username'
role:'test'
}
]
I suppose you need the following
let db1 = db.get().collection(`temp1`);
let db2 = db.get().collection(`temp2`);
await db1.aggregate([
{
$lookup: {
from: "temp2",
localField: "_id", // field in the orders collection
foreignField: "_id", // field in the items collection
as: "users"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [{ $arrayElemAt: ["$users", 0] }, "$$ROOT"] } }
},
{ $project: { users: 0 } }
]).toArray()

How to lookup inside lookup in MongoDB Aggregate?

I have a simple 3 collections. This bellow is their pseudocode. I want to get all shipments and for each shipment, I want to have all bids for that shipment and in each bid, I need userDetails object.
User: {
name: string,
}
Shipment: {
from: string,
to: string
}
Bid: {
amount: number,
shipmentId: Ref_to_Shipment
userId: Ref_to_User
}
This is what I have tried:
const shipments = await ShipmentModel.aggregate([
{
$lookup: {
from: "bids",
localField: "_id",
foreignField: "shipmentId",
as: "bids"
}
},
{
$lookup: {
from: "users",
localField: "bids.userId",
foreignField: "_id",
as: "bids.user"
}
}
])
And I got the following result:
[
{
"_id": "5fad2fc04458ac156531d1b1",
"from": "Belgrade",
"to": "London",
"__v": 0,
"bids": {
"user": [
{
"_id": "5fad2cdb4d19c80d1b6abcb7",
"name": "Amel",
"email": "Muminovic",
"password": "d2d2d2",
"__v": 0
}
]
}
}
]
I am trying to get all Shipments with their bids and users within bids. Data should look like:
[
{
"_id": "5fad2fc04458ac156531d1b1",
"from": "Belgrade",
"to": "London",
"__v": 0,
"bids": [
{
"_id": "5fad341887c2ae1feff73402",
"amount": 400,
"userId": "5fad2cdb4d19c80d1b6abcb7",
"shipmentId": "5fad2fc04458ac156531d1b1",
"user": {
"name": "Amel",
}
"__v": 0
}
]
}
]
Try with the following code:
const shipments = await ShipmentModel.aggregate([
{
$lookup: {
from: "bids",
localField: "_id",
foreignField: "shipmentId",
as: "bids"
}
},
{
$unwind: {
path: "$bids",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: "users",
localField: "bids.userId",
foreignField: "_id",
as: "bids.user"
}
}
])
If you want to prevent null and empty arrays then set
preserveNullAndEmptyArrays: false
Try this query and chek if works and the behaviour is as you expected:
db.Shipment.aggregate([
{
$lookup: {
from: "Bid",
localField: "id",
foreignField: "shipmentId",
as: "bids"
}
},
{
$lookup: {
from: "user",
localField: "id",
foreignField: "id",
as: "newBids"
}
},
{
$project: {
"newBids.id": 0,
"newBids._id": 0,
}
},
{
$match: {
"bids.userId": 1
}
},
{
$addFields: {
"newBids": {
"$arrayElemAt": [
"$newBids",
0
]
}
}
},
{
$set: {
"bids.user": "$newBids"
}
},
{
$project: {
"newBids": 0
}
}
])
This query do your double $lookup and then a $project to delete the fields you don't want, and look for the userId to add the field user. As $lookup generate an array, is necessary use arrayElemAt to get the first position.
Then $set this value generated into the object as bids.user and remove the old value.
Note that I have used a new field id instead of _id to read easier the data.
Try this
I figured out it based on MongoDB $lookup on array of objects with reference objectId and in the answer from J.F. (data organization). Note that he used id instead of _id
The code is
db.Shipment.aggregate([
{
$lookup: {
from: "Bid",
localField: "id",
foreignField: "shipmentId",
as: "bids"
}
},
{
$lookup: {
from: "user",
localField: "bids.userId",
foreignField: "id",
as: "allUsers"
}
},
{
$set: {
"bids": {
$map: {
input: "$bids",
in: {
$mergeObjects: [
"$$this",
{
user: {
$arrayElemAt: [
"$allUsers",
{
$indexOfArray: [
"$allUsers.id",
"$$this.userId"
]
}
]
}
}
]
}
}
}
}
},
{
$unset: [
"allUsers"
]
},
// to get just one
//{
// $match: {
// "id": 1
// }
// },
])

Populate one collection with lookup in mongoose

I'm trying to populate a collection with lookup. The thing is I need to join three collections at once using mongoose in express js.
I have three collections namely, users, skills, userskills.
User and UserSkills are connected. Skills and UserSkills are connected. But not User and Skills.
My model is like
users
{
_id: 5ec6d940b98e8f2c3cea5f22,
name: "test one",
email: "test#example.com"
}
skills
{
_id: 5ec786b21cea7d8c8c186a54,
title: "java"
}
user-skills
{
_id: 5ec7879c1cea7d8c8c186a56,
skillId: "5ec786b21cea7d8c8c186a54",
userId: "5ec6d940b98e8f2c3cea5f22",
level: "good"
}
I tried
user = await User.aggregate([{
$lookup: {
from: "userskills",
localField: "_id",
foreignField: "userId",
as: "userskills"
}
}, {
$unwind: {
path: "$userskills",
preserveNullAndEmptyArrays: true
}
}, {
$lookup: {
from: "skills",
localField: "userskills.skillId",
foreignField: "_id",
as: "userskills.skill",
}
}, {
$group: {
_id : "$_id",
name: { $first: "$name" },
skill: { $push: "$skills" }
}
}, {
$project: {
_id: 1,
name: 1,
skills: {
$filter: { input: "$skills", as: "a", cond: { $ifNull: ["$$a._id", false] } }
}
}
}]);
Required result:
{
"users" : [
{
_id: "5ec6d940b98e8f2c3cea5f22"
name: "test one",
email: "testone#example.com",
"skills" : [
{
_id: "5ec7879c1cea7d8c8c186a56",
level: "good",
"skill" : {
_id: "5ec786b21cea7d8c8c186a54",
title: "java"
}
}
]
},
{
_id: "5ec6d940b98e8f2c3cea5f23"
name: "test two",
email: "testtwo#example.com",
"skills" : [
{
_id: "5ec7879c1cea7d8c8c186a57",
level: "good",
"skill" : {
_id: "5ec786b21cea7d8c8c186a55",
title: "php"
}
}
]
}
]
}
when using
user = await User.find().populate('Skills").exec()
The result is coming like
{
"users" : [
{
_id: "5ec6d940b98e8f2c3cea5f22"
name: "test one",
email: "testone#example.com",
"skills" : [
{
_id: "5ec7879c1cea7d8c8c186a56",
level: "good",
skillId: "5ec786b21cea7d8c8c186a55"
}
]
}
]
}
The problem is I need the skill name should also be fetched. Please help me to solve this issue. I'm writing a backend API in nodejs and mongodb.
You can embed that second $lookup as part of custom pipeline:
await User.aggregate([
{
$lookup: {
from: "userskills",
let: { user_id: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: [ "$$user_id", "$userId" ] } } },
{
$lookup: {
from: "skills",
localField: "skillId",
foreignField: "_id",
as: "skill"
}
},
{ $unwind: "$skill" }
],
as: "skills"
}
}
])
Mongo Playground

Best a very overloaded query mongodb - Nodejs; aggregate, group, porject and lookup

its my first question in this forum. (I am a Spanish speaker, sorry for my basic English)
I have 3 collections: images, artists and exhibitions.
Images has: filename (unique), path and _id and etc.
Artists has: name, url (unique) and description and etc
Exhibitions has: name, url (unique), year, abstract, aristUrl (artist.url) imageCover (image.fieldname) and etc.
Whit my query this is my summary result:
[
_id: null,
documents: [{...}, {...}],
totalExhibitions: 2
]
Full result
[
{
"_id": null,
"documents": [
{
"_id": "5e84d6599891212db0a6dc7e",
"url": "chile",
"imageCover": [
{
"path": "http://localhost:2616/uploads/images/1585763637647.jpg"
}
],
"name": "Almacén Verdad y Justicia",
"year": 2010,
"releaseDate": "2010-08-30T00:00:00.000Z",
"artist": [
{
"name": "Bernardo de Castro Saavedra",
"url": "bernardo-castro-saavedra"
}
]
},
{
"_id": "5e84e0575a3f201aac2df1c2",
"url": "sin-cera",
"imageCover": [
{
"path": "http://localhost:2616/uploads/images/1585766437587.jpg"
}
],
"name": "Sin cera",
"year": 2020,
"releaseDate": "2020-01-31T00:00:00.000Z",
"artist": [
{
"name": "Gonzalo Tapia",
"url": "gonzalo-tapia"
}
]
}
],
"totalExhibitions": 2
}
]
This is my code
getByLastYear(){
const documents = this.mongoDB.aggregate([
{
$addFields: {
"artistObjectId": { $toObjectId: "$artistId" }
}
},{
$lookup: {
from: 'artists',
localField: 'artistObjectId',
foreignField: "_id",
as: "artist"
},
},{
$lookup: {
from: 'images',
localField: 'imageCover',
foreignField: "filename",
as: "imageCover"
},
},{
$project: {
name: 1,
year: 1,
url: 1,
releaseDate: 1,
artist: {
name: 1,
url: 1
},
imageCover: {
path: 1,
alt: 1,
}
}
}, {
$group: {
_id: null,
documents: {
$push: "$$ROOT"
},
totalExhibitions: {
$sum: 1
}
}
}
]);
return documents || [];
};
that is the best form to get my result?
is there any better?
Thank you for your comments and opinions.<3
Assuming that:
you're starting from the exhibitions collection;
the "_id" fields have an ObjectId format;
there's only 1 artist and 1 cover image for each exhibition...
I'd skip the first step and add in $unwind stages to have the artist and the cover image as a subdocument instead of an array.
The reason for that is that it will make it easier to reference the result.
This should be able to run in your Mongo shell:
db.exhibitions.aggregate([
{ $lookup: {
from: 'artists',
localField: "artistId",
foreignField: "_id",
as: "artist"
}},
{ $unwind: {
path: "$artist",
preserveNullAndEmptyArrays: true
}},
{ $lookup: {
from: 'images',
localField: 'imageCover',
foreignField: "filename",
as: "imageCover"
}},
{ $unwind: {
path: "$imageCover",
preserveNullAndEmptyArrays: true
}},
{ $project: {
name: 1,
year: 1,
url: 1,
releaseDate: 1,
artist: {
name: "$artist.name",
url: "$artist.url"
},
imageCover: {
path: "$imageCover.path",
alt: "$imageCover.alt",
}
}},
{ $group: {
_id: null,
documents: {
$push: "$$ROOT"
},
totalExhibitions: {
$sum: 1
}
}}
]);
In the "$project" stage, for artist and imageCover, you would need to specify the full path to build the reduced subdocument.
Hope that answered your question...

Resources