Nodejs localField $lookup as Array of _ids - node.js

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();

Related

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 in another collection with objectID of main collection in mongodb

I am trying to get the data from another collection via lookup
collection "users"
{
"_id":{
"$oid":"60bf4bb31f45d98903d1851f"
},
"name":"Dave",
"center":"THGJ556",
}
collection "addresses"
{
"_id":{
"$oid":"60bf4bb31f45d98903d1851f"
},
"userId":"60bf4bb31f45d98903d1851f",
}
collection "applications"
{
"_id":{
"$oid":"60bf4bb31f45d98903d1851f"
},
"userId":"60bf4bb31f45d98903d1851f",
"centerId":"THGJ556",
},
{
"_id":{
"$oid":"60bf4bb31f45d98903d3647j"
},
"userId":"60bf4bb31f45d98903d1851f",
"centerId":"JHGJ5476",
}
Now I need data from all the tables.
here is my code:
users.aggregate([
{
$lookup: {
from: "addresses",
localField: "_id",
foreignField: "userId",
as: "addressData"
}
},
{
$lookup: {
from: "applications",
pipeline: [
{ $match:
{ userId:"$_id", centerId: "JHGJ5476"}
},
],
as: "applicationData"
}
},
] ,function(err, result) {
if (err) {
console.log(err)
} else {
console.log(result)
}
});
I am doing something wrong while using aggregate and match in pipeline.
I am getting addressData correctly, but I get nothing [] in applicationData because I suspect something is wrong with userId:"$_id"
As docs explain:
A $match stage requires the use of an $expr operator to access the variables. $expr allows the use of aggregation expressions inside of the $match syntax.
So you have to use $expr into $match stage and also a let stage.
let stage is to define variable to use into $expr: id: $_id.
$expr used with $and and $eq is to get both conditions.
db.users.aggregate([
{
$lookup: {
from: "addresses",
localField: "_id",
foreignField: "userId",
as: "addressData"
}
},
{
$lookup: {
from: "applications",
let: {"id": "$_id"},
pipeline: [
{
$match: {
"$expr": {
"$and": [
{"$eq": ["$userId","$$id"]},
{"$eq": ["$centerId","JHGJ5476"]}
]
}
}
}
],
as: "applicationData"
}
}
])
Check this example.

MongoDB $lookup in different collections by _id

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"
}
},
])

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
// }
// },
])

Getting Mongoose Error: Arguments must be aggregate pipeline operators

I'm trying to run this aggregations but I cannot get a result with aggregation option parameter, what is wrong ? Here is my aggregation:
var coupons = couponModel.aggregate(
[
{
"$lookup": {
from: "campaigns",
localField: "campaignId",
foreignField: "_id",
as: "campaigns"
}
},
{ "campaignId": { "$in": campaignsIds } },
],
{
allowDiskUse: true,
explain: true
}
);
Also Try Different Structure:
couponModel.aggregate(
[
{
"$lookup": {
from: "campaigns",
localField: "campaignId",
foreignField: "_id",
as: "campaigns"
}
},
{ "campaignId": { "$in": campaignsIds } },
],
function(err,result) {
if (err) return handleError(err);
// Result is an array of documents
console.log(result);
}
)
When using mongoose you cannot pass an object as the second parameter of aggregate(). Instead you must use mongoose functions. Also, in the second object of your pipeline you forgot to specify the aggregation stage. So your code would look like this:
var coupons = couponModel.aggregate(
[
{
"$lookup": {
from: "campaigns",
localField: "campaignId",
foreignField: "_id",
as: "campaigns"
}
},
{ $match: { "campaignId": { "$in": campaignsIds } } }
]
)
.allowDiskUse(true)
.explain(true);

Resources