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();
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"
}
},
])
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
// }
// },
])
This is my user document
{
"_id":"02a33b9a-284c-4869-885e-d46981fdd679",
"context":{
"email":"someemail#gmail.com",
"firstName":"John",
"lastName":"Smith",
"company":[
"e2467c93-114b-4613-a842-f311a8c537b3"
],
},
}
and a company document
{
"_id":"e2467c93-114b-4613-a842-f311a8c537b3",
"context":{
"name":"Coca Cola",
"image":"someimage",
},
};
This is my query for users
let users = await Persons.aggregate(
[{$project:
{
name: {$concat: ['$context.firstName', ' ', '$context.lastName']},
companyId: {$arrayElemAt: ['$context.company', 0]}}
},
{$match: {name: searchRegExp}},
{$lookup: {from: 'companies', let: {company_id: {$arrayElemAt: ['$context.company', 0]}}, pipeline:
[
{
$match: {
$expr: {
$eq: ['$_id', '$$company_id']
}
}
},
{
$project: {name: '$context.name'}
}
],
as: 'company'}}
]).toArray()
When I run this query I get company field as an empty array, what am I doing wrong here?
Your first pipeline stage $project only outputs _id, name and companyId so then when you're trying to refer to $context.company in your $lookup there will be an empty value. You can use $addFields instead:
{
$addFields: {
name: {
$concat: [
"$context.firstName",
" ",
"$context.lastName"
]
},
companyId: {
$arrayElemAt: [
"$context.company",
0
]
}
}
}
Mongo Playground
When you add field companyId: {$arrayElemAt: ['$context.company', 0]}}, then you can use the simple version of $lookup. There is no need to set it twice, once as companyId: ... and in let: {company_id: ...}
db.user.aggregate([
{
$addFields: {
name: { $concat: ["$context.firstName", " ", "$context.lastName"] },
companyId: { $arrayElemAt: ["$context.company", 0] }
}
},
{
$lookup: {
from: "company",
localField: "companyId",
foreignField: "_id",
as: "company"
}
}
])
I have the following 3 collections schema in my Node.js app
Users{
_id
Name
}
Vendors{
_id
userId
CompanyName
}
CustomerFavoriteVendors{
_id
customerId
vendorId
}
Now I am trying to retrieve a list of all Vendors, while displaying their full user details as well as if they are Favorited by currently logged in customer, so I've built the following vendors aggregate function yet I can only retrieve vendors whom were Favorited, so I am wondering what I am missing here to retrieve all vendors despite if they were favorited or not? Thanks for your effort and support
let size = 10;
let offset = 0;
let query = [];
query.push(
{ $lookup: { from: "users", localField: "userId", foreignField: "_id", as: "userdetail" } },
{ $unwind: "$userdetail" },
{ $lookup: { from: "customerfavoritevendors", localField: "_id", foreignField: "vendorId", as: "customerfav" } },
{ $unwind: "$customerfav" },
{ $group: { _id: null, content: { $push: '$$ROOT' },count: { $sum: 1 } } },
{ $project: { content: { $slice: [ '$content', offset, size ] }, count: 1, _id: 1 } },
);
const vendorsList = await Vendors.aggregate(query);