$lookup do not work with field starting with a $ - mongodb

b.getCollection('actions').aggregate([
{ $lookup : {
from: "companies",
localField: "company.$id",
foreignField: "companies:_id",
as: "companyId"
}
}
])
Error message : fieldpath field names cannot start with $.
I cannot rename the field, it's a production site, and if I cannot do a simple JOIN I'm going back to SQL.
b.getCollection('actions').aggregate([
{ $lookup : {
from: "companies",
localField: "company.$id",
foreignField: "companies:_id",
as: "companyId"
}
}
])

DBrefs will have fields starting with '$', so direclty we cannot use
$lookup on DBrefs.
using $objectToArray, $lookup can be used
( $objectToArray is supported from version 3.4.4 and above)
db.getCollection('actions').aggregate([
{
$project:
{
companiesArray: { $objectToArray: "$company" }
}
},
{
$unwind: "$companiesArray"
},
{
$match: {"companiesArray.k" :"$id"}
},
{ $lookup : {
from: "companies",
localField: "companiesArray.v",
foreignField: "_id",
as: "companyId"
}
}
])

you have syntax errors in your "JOINT", it should look like this
b.getCollection('actions').aggregate([
{
$lookup : {
from: "companies",
localField: "_id",
foreignField: "companies._id",
as: "companyId"
}
}
])
- I'm assuming thats how your schema looks, cant really know unless you share it.

Related

condition in mondodb aggregation

I'm using MongoDB. I need different types of booking(booking, classBooking, workshopBooking, membershipBooking). There are 2 statuses of booking (approve, rejected). I need only approved booking.
Problem is when there were not approved booking in any specific field entire records get empty.
I have coded as below.
var payData = await countryModel
.aggregate([
{ $match: filter },
{
$lookup: {
from: "venues",
localField: "_id",
foreignField: "country",
as: "venue",
},
},
{
$lookup: {
from: "bookings",
localField: "venue._id",
foreignField: "venue",
as: "booking",
},
},
// { $match: { "booking.status": "approve" } },
{
$lookup: {
from: "classbookings",
localField: "venue._id",
foreignField: "venue",
as: "classbooking",
},
},
// { $match: { "classbooking.status": "approve" } },
{
$lookup: {
from: "workshobookings",
localField: "venue._id",
foreignField: "venue",
as: "workshopbooking",
},
},
// { $match: { "workshopbooking.status": "approve" } },
{
$lookup: {
from: "membershipbookings",
localField: "venue._id",
foreignField: "venue",
as: "membershipbooking",
},
},
// { $match: { "membershipbooking.status": "approve" } },
])
.exec();
Suppose there are membership booking, but not other booking. I get empty because pipeline get empty because of previous records empty records.
I want if there are not approve records then that field should empty list'[]', otherwise list of that booking

Join multiple collections in mongodb and keep all fields

I am new to NoSQL databases, and I got a little confused with collection aggregation. Here is what I am trying to do: I have three collections: productCollection, detailsCollection and brandCollection.
productCollection has the following fields: _id, name, details
detailsCollection has _id and brand
brandCollection has _id and name
These collections also have other fields, but these are the most interesting for me. As you may guess, details in productCollection is a reference to _id in detailsCollection, while brand in detailsCollection is a reference to _id in brandCollection. What I need is to get the collection with products and their brands. So, basically, I need to join these three collections and extract name from productCollection and name from brandCollection
So far, I managed to write this script:
db.productCollection.aggregate([
{
$lookup: {
from: "detailsCollection",
localField: "details",
foreignField: "_id",
as: "det"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$det", 0 ] }, "$$ROOT" ] } }
},
{ $project: { "det": 0 } },
{
$lookup: {
from: "brandCollection",
localField: "brand",
foreignField: "_id",
as: "br"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$br", 0 ] }, "$$ROOT" ] } }
},
{ $project: { "br": 0 } }
])
It shows me all the fields in all three collections, but it does not show me the brand's name. I think it might be because the field name appears in both productCollection and brandCollection. All other fields are fine.
Hence, my question is: how do I make name from brandCollection appear in the result too? Maybe I can rename it in the process to be shown under another name? And is there an easier way to join these three collections? Or is the script above fine?
Thank you for any help!
$lookup with detailsCollection collection
$lookup with brandCollection and pass localField as brand id
$arrayElemAt to get first element from brand result
remove details field its no longer needed
db.productCollection.aggregate([
{
$lookup: {
from: "detailsCollection",
localField: "details",
foreignField: "_id",
as: "brand"
}
},
{
$lookup: {
from: "brandCollection",
localField: "brand.brand",
foreignField: "_id",
as: "brand"
}
},
{
$addFields: {
brand: {
$arrayElemAt: ["$brand.name", 0]
},
details: "$$REMOVE"
}
}
])
Playground

MongoDb aggregate check if id exists in array of objects

I have written this aggregate and it works fine
db.ParcelStatus.aggregate([{
{
$lookup: {
from: "Parcel",
localField: "parcelId",
foreignField: "_id",
as: "parcel"
}
},
{
$unwind: {
path: "$parcel",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: "ParcelStatus",
localField: "parcel._id",
foreignField: "parcelId",
as: "parcel.parcelStatuses"
}
},
{
$lookup: {
from: "Customer",
localField: "parcel.customerData.customerId",
foreignField: "_id",
as: "parcel.customerData.customer"
}
},
{
$unwind: "$parcel.customerData.customer"
}
])
Now in ParcelStatus array that is INCLUDED inside PARCEL object i need to check that
if(parcel.ParcelStatus.includes((x) => x.statusRepositoryId === 'ID's from frontend')
//then run $match on root against statusRepositoryId === 'SPECIFIC STATIC ID'
I don't know how i can do that inside aggregate. Your help will be much appreciated
Just add this $match stage:
{
$match: {
"parcel.parcelStatuses.statusRepositoryId": {$in: idArrayFromClient}
}
}

MongoDB Aggregation - project after lookup [duplicate]

In mongo, after doing an aggregation with $lookup, I would like the request to return only some fields and not the whole document.
I have the following query :
db.somecollection.aggregate([{
$lookup: {
from: "campaigns",
localField: "campId",
foreignField: "_id",
as: "campaign"
}
}, {
$unwind: "$campaign"
}, {
$lookup: {
from: "entities",
localField: "campaign.clientid",
foreignField: "_id",
as: "campaign.client"
}
}]);
This request will return me this :
{
"_id" : ObjectId("56cc7cd1cc2cf62803ebfdc7"),
"campId" : ObjectId("56c740e4479f46e402efda84"),
"articleId" : ObjectId("56c742c06094640103ba3843"),
"campaign" : {
"_id" : ObjectId("56c740e4479f46e402efda84"),
"clientid" : ObjectId("56c740b8479f46e402efda83"),
"client" : [
{
"_id" : ObjectId("56c740b8479f46e402efda83"),
"username" : "someusername",
"shhh" : "somehashedpassword",
"email" : "mail#mail.com",
}
]
}
The request works well, but I would like to filter the fields in campaign.client to only get for example _id and username. Is there a way to do this in a MongoDB aggregate request?
Just to help others with this, #SiddhartAjmera has the right answer, I only needed to add double quotes for nested values like "campaign.clientid".
The final code should be:
db.somecollection.aggregate([
{
"$lookup": {
"from": "campaigns",
"localField": "campId",
"foreignField": "_id",
"as": "campaign"
}
},
{
"$unwind": "$campaign"
},
{
"$lookup": {
"from": "entities",
"localField": "campaign.clientid",
"foreignField": "_id",
"as": "campaign.client"
}
},
{
"$project": {
"_id": 1,
"campId": 1,
"articleId": 1,
"campaign._id": 1,
"campaign.clientid": 1,
"campaign.client._id": 1,
"campaign.client.username": 1
}
}
]);
Using pipeline and $project inside $lookup
db.somecollection.aggregate([{
$lookup: {
from: "campaigns",
localField: "campId",
foreignField: "_id",
as: "campaign"
}
}, {
$unwind: "$campaign"
}, {
$lookup: {
from: "entities",
let: { client_id: "$campaign.clientid" },
pipeline : [
{ $match: { $expr: { $eq: [ "$_id", "$$client_id" ] } }, },
{ $project : { _id:1, username:1 } }
],
as: "campaign.client"
}
}]);
Just to add a little thing to the previous answer: You can put a 0 to a project item that you want to ignore and the rest will be retrieved, so you don´t need to write all the list with 1:
db.somecollection.aggregate([
{
"$lookup": {
"from": "campaigns",
"localField": "campId",
"foreignField": "_id",
"as": "campaign"
}
},
{
"$unwind": "$campaign"
},
{
"$lookup": {
"from": "entities",
"localField": "campaign.clientid",
"foreignField": "_id",
"as": "campaign.client"
}
},
{
"$project": {
"campaign.client.shhh": 0
}
}
])
I know this is very late to answer to this question. But in my opinion, an update can sometimes prove to be very beneficial.
The project stage is great but you'd still be requesting for the entire dock in the $lookup stage. The fields are only filtered in the projection stage following it.
After the release of MongoDB 3.6, you can now add a pipeline to a $lookup stage, to specify multiple join conditions. Find more details in their official docs.
Specify Multiple Join Conditions with $lookup
You can transform your aggregation pipeline as follows, to get the desired result:
db.somecollection.aggregate([{
$lookup: {
from: "campaigns",
localField: "campId",
foreignField: "_id",
as: "campaign"
}
}, {
$unwind: "$campaign"
}, {
$lookup: {
from: "entities",
let: {clientid: '$campaign.clientid'},
pipeline: [
{ '$match':
{ '$expr':
{
'$eq': ['$_id', '$$clientid']
}
}
},
{ '$project':
'_id': 1,
'username': 1
}
]
as: "campaign.client"
}
}]);
This way you can filter the fields of the joined collection right inside the $lookup stage.
Notice the $$ sign inside the $match stage of inner pipeline. It is used to denote a custom field defined inside the let block.

project some fields in $lookup mongodb [duplicate]

In mongo, after doing an aggregation with $lookup, I would like the request to return only some fields and not the whole document.
I have the following query :
db.somecollection.aggregate([{
$lookup: {
from: "campaigns",
localField: "campId",
foreignField: "_id",
as: "campaign"
}
}, {
$unwind: "$campaign"
}, {
$lookup: {
from: "entities",
localField: "campaign.clientid",
foreignField: "_id",
as: "campaign.client"
}
}]);
This request will return me this :
{
"_id" : ObjectId("56cc7cd1cc2cf62803ebfdc7"),
"campId" : ObjectId("56c740e4479f46e402efda84"),
"articleId" : ObjectId("56c742c06094640103ba3843"),
"campaign" : {
"_id" : ObjectId("56c740e4479f46e402efda84"),
"clientid" : ObjectId("56c740b8479f46e402efda83"),
"client" : [
{
"_id" : ObjectId("56c740b8479f46e402efda83"),
"username" : "someusername",
"shhh" : "somehashedpassword",
"email" : "mail#mail.com",
}
]
}
The request works well, but I would like to filter the fields in campaign.client to only get for example _id and username. Is there a way to do this in a MongoDB aggregate request?
Just to help others with this, #SiddhartAjmera has the right answer, I only needed to add double quotes for nested values like "campaign.clientid".
The final code should be:
db.somecollection.aggregate([
{
"$lookup": {
"from": "campaigns",
"localField": "campId",
"foreignField": "_id",
"as": "campaign"
}
},
{
"$unwind": "$campaign"
},
{
"$lookup": {
"from": "entities",
"localField": "campaign.clientid",
"foreignField": "_id",
"as": "campaign.client"
}
},
{
"$project": {
"_id": 1,
"campId": 1,
"articleId": 1,
"campaign._id": 1,
"campaign.clientid": 1,
"campaign.client._id": 1,
"campaign.client.username": 1
}
}
]);
Using pipeline and $project inside $lookup
db.somecollection.aggregate([{
$lookup: {
from: "campaigns",
localField: "campId",
foreignField: "_id",
as: "campaign"
}
}, {
$unwind: "$campaign"
}, {
$lookup: {
from: "entities",
let: { client_id: "$campaign.clientid" },
pipeline : [
{ $match: { $expr: { $eq: [ "$_id", "$$client_id" ] } }, },
{ $project : { _id:1, username:1 } }
],
as: "campaign.client"
}
}]);
Just to add a little thing to the previous answer: You can put a 0 to a project item that you want to ignore and the rest will be retrieved, so you don´t need to write all the list with 1:
db.somecollection.aggregate([
{
"$lookup": {
"from": "campaigns",
"localField": "campId",
"foreignField": "_id",
"as": "campaign"
}
},
{
"$unwind": "$campaign"
},
{
"$lookup": {
"from": "entities",
"localField": "campaign.clientid",
"foreignField": "_id",
"as": "campaign.client"
}
},
{
"$project": {
"campaign.client.shhh": 0
}
}
])
I know this is very late to answer to this question. But in my opinion, an update can sometimes prove to be very beneficial.
The project stage is great but you'd still be requesting for the entire dock in the $lookup stage. The fields are only filtered in the projection stage following it.
After the release of MongoDB 3.6, you can now add a pipeline to a $lookup stage, to specify multiple join conditions. Find more details in their official docs.
Specify Multiple Join Conditions with $lookup
You can transform your aggregation pipeline as follows, to get the desired result:
db.somecollection.aggregate([{
$lookup: {
from: "campaigns",
localField: "campId",
foreignField: "_id",
as: "campaign"
}
}, {
$unwind: "$campaign"
}, {
$lookup: {
from: "entities",
let: {clientid: '$campaign.clientid'},
pipeline: [
{ '$match':
{ '$expr':
{
'$eq': ['$_id', '$$clientid']
}
}
},
{ '$project':
'_id': 1,
'username': 1
}
]
as: "campaign.client"
}
}]);
This way you can filter the fields of the joined collection right inside the $lookup stage.
Notice the $$ sign inside the $match stage of inner pipeline. It is used to denote a custom field defined inside the let block.