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
// }
// },
])
Related
i need to join two documents where in the first there a property as array of _ids and in the second documents to join
db={
"tipo_pratica": [
{
"_id": "618981a4c1b8b3bc67ff80b6",
"descrizione": "anticipata",
"modulo": [
"628015cd2fd9dfee86ac6820",
"62801a4c2fd9dfee86ac6821",
"6278f8d9d4aa4f4cef1a8266"
]},
{
"_id": "628238d6f97b57efcb1fc504",
"descrizione": "Supporto",
"modulo": [
"6278f8d9d4aa4f4cef1a8266",
"628015cd2fd9dfee86ac6820",
"62801a4c2fd9dfee86ac6821"
]}
]};
db={
"moduli": [
{
"_id": "6278f8d9d4aa4f4cef1a8266",
"tipo": "Incentivi auto",
"documento": [
"1652095190015_la_scuola_2021_copia.pdf"
],
"contenuto": "<p>Inserire il documento allegato</p>"
},
{
"_id": "628015cd2fd9dfee86ac6820",
"tipo": "Mandato di assistenza e rappresentanza",
"documento": [
"1652561335432_Mandato_di_rappresentanza_privacy_0.pdf"
],
"contenuto": "<p>no</p>"
},
{
"_id": "62801a4c2fd9dfee86ac6821",
"tipo": "Modello red da far... ",
"documento": [
"1652562502599_Modello_REX.pdf"
],
"contenuto": null
}]
};
as documentation said:
Use $lookup with an Array
I tried:
const doc = await collection.aggregate([
{
$lookup: {
from: "moduli",
localField: "modulo",
foreignField: "_id",
as: "moduls"
}
}
])
with no success
so i tested the script on mongoplayground
and there it seems to work well.
I think the problem reside in array of Ids, also
i have tried many option, i have often read the documentation and serching on the web, but many results are specific to mongoose drive, while i use native drive.
I would like the same return as the playground example.
So, any help is largely apprecciate.
below the snippet i use in node for make call
app.post('/admin/moduli/join/', (req, res, error) => {
async function run() {
try {
await client.connect();
var ObjectId = require('mongodb').ObjectId;
const db = client.db("admin");
const collection = db.collection("tipo_pratica");
// replace IDs array with lookup results passed to pipeline
const doc = await collection.aggregate([
{
$lookup: {
from: "moduli",
localField: "modulo",
foreignField: "_id",
pipeline: [
{ $match: { $expr: {$in: ["$_id", "$$modulo"] } } },
{ $project: {_id: 0} } // suppress _id
],
as: "productObjects"
}
}
]);
// doc not work!
// Unwind
const doc2 = await collection.aggregate([
// Unwind the source
{ "$unwind": "$modulo" },
// Do the lookup matching
{ "$lookup": {
"from": "moduli",
"localField": "modulo",
"foreignField": "_id",
"as": "productObjects"
}
}
// doc2 not work!
const doc3 = await collection.aggregate([
{
$facet: {
moduli: [
{
$lookup: {
from: "moduli",
localField: "modulo",
foreignField: "_id", // also tried ObjectId()
as: "plugin"
}
},
{
$match: {
plugin: {
$not: {
$size: 0
}
}
}
}
]
}
},
{
$project: {
tipoPratiche: {
"$concatArrays": [
"$moduli"
]
}
}
},
{
$unwind: "$tipoPratiche"
},
]).toArray();
// doc3 not work!
res.send(doc4);
} finally {
await client.close();
}
}
run().catch(console.dir);
});
Thank you in advance for your time
One way to do it is using a $map before the $lookup:
db.tipo_pratica.aggregate([
{
$set: {
modulo: {
$map: {
input: "$modulo",
as: "item",
in: {$toObjectId: "$$item"}
}
}
}
},
{
$lookup: {
from: "moduli",
localField: "modulo",
foreignField: "_id",
as: "moduls"
}
}
])
Playground
Another option is to use a $lookup pipeline and cast the string to ObjectId inside it.
The proble reside in the formatting text. So belove the solution
const doc1 = await db.collection("tipo_pratica").aggregate([
{
'$set': {
'modulo': {
'$map': {
'input': '$modulo',
'as': 'item',
'in': {
'$toObjectId': '$$item'
}
}
}
}
}, {
'$lookup': {
'from': 'moduli',
'localField': 'modulo',
'foreignField': '_id',
'as': 'moduls'
}
}
]).toArray();
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()
I have 3 mongoDB collections
I need to aggregate them with $lookup operator but I didn't find anything/**or I'm bad looking **
1st one is suppliers
{
"_id" : ObjectId("111"), //for example, in db is mongodb ids
"name" : "supplier 1",
}
{
"_id" : ObjectId("222"),
"name" : "supplier 1",
}
2nd one is clients
{
"_id" : ObjectId("333"), //for example, in db is mongodb ids
"name" : "clients 1",
}
{
"_id" : ObjectId("444"),
"name" : "clients 2",
}
and 3rd is moves
{
"_id" : ObjectId("..."), //for example, in db is mongodb ids
"moveName" : "move 1",
"agent": ObjectId("111") // this is from suppliers collection
}
{
"_id" : ObjectId("..."),
"moveName" : "move 2",
"agent": ObjectId("333") // this one is from CLIENTS collection
}
so like output I need data like this
{
"_id" : ObjectId("..."), //for example, in db is mongodb ids
"moveName" : "move 1",
**"agent": supplier 1** // this is from suppliers collection
}
{
"_id" : ObjectId("..."),
"moveName" : "move 2",
**"agent": clients 1** // this one is from CLIENTS collection
}
back end is nodejs, I`m using mongoose, how I can search in 2nd collection if noresult in 1st?
const moves = await Move.aggregate([
{ $match: query }, // here all wokrs good
{
$lookup: {
from: 'clients',
localField: 'agent',
foreignField: '_id',
as: 'agent'
}
},{ $unwind: {path: "$agent" , preserveNullAndEmptyArrays: true} },
{
$lookup: {
from: 'suppliers',
localField: 'agent',
foreignField: '_id',
as: 'agent2'
}
},
{
$project: {
operationName: 1,
agent: {$ifNull: ['$agent.name', '$agent2.name']}
}
}
])
Thank You!
As suggested by #hhharsha36, we can use $facet operator which allows to run several pipelines within a single stage.
Explanation
facet
suppliers = $lookup suppliers collection and filter only matched results
clientes = $lookup clientes collection and filter only matched results
concatArrays = We concat suppliers and clients results into a single movies array
unwind = We flatten movies array [a, b, c] -> a
b
c
replaceWith = We replace the root element [movies:a, movies:b -> a, b]
mergeObject = allows us to pick the agent name (this way we avoid 1 more stage)
db.moves.aggregate([
{
$facet: {
suppliers: [
{
$lookup: {
from: "suppliers",
localField: "agent",
foreignField: "_id",
as: "agent"
}
},
{
$match: {
agent: {
$not: {
$size: 0
}
}
}
}
],
clients: [
{
$lookup: {
from: "clients",
localField: "agent",
foreignField: "_id",
as: "agent"
}
},
{
$match: {
agent: {
$not: {
$size: 0
}
}
}
}
]
}
},
{
$project: {
movies: {
"$concatArrays": [
"$clients",
"$suppliers"
]
}
}
},
{
$unwind: "$movies"
},
{
$replaceWith: {
"$mergeObjects": [
"$movies",
{
agent: {
"$arrayElemAt": [
"$movies.agent.name",
0
]
}
}
]
}
}
])
MongoPlayground
This aggregation query gives the desired result:
db.moves.aggregate([
{
$lookup: {
from: "suppliers",
localField: "agent",
foreignField: "_id",
as: "moves_sup"
}
},
{
$unwind: { path: "$moves_sup" , preserveNullAndEmptyArrays: true }
},
{
$lookup: {
from: "clients",
localField: "agent",
foreignField: "_id",
as: "moves_client"
}
},
{
$unwind: { path: "$moves_client" , preserveNullAndEmptyArrays: true }
},
{
$addFields: {
agent: {
$cond: [ { $eq: [ { $type: "$moves_sup" }, "object" ] },
"$moves_sup.name",
{ $cond: [ { $eq: [ { $type: "$moves_client" }, "object" ] }, "$moves_client.name", "undefined" ] }
] },
moves_client: "$$REMOVE",
moves_sup: "$$REMOVE"
}
},
])
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...
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.