I have two collections Projects and Users.
Projects has a budget field with the following: 1)amount and 2)currency
{
_id: ObjectId(...),
type: 'typeA',
budget: {
amount: 123,
currency: 'USD'
}
}
Users has a field called bids which contains list of objects with fields amount and currency.
{
_id: ObjectId(...),
name: "User name",
bids: [{amount: 123, currency: "USD"}, {amount: 342, currency: "INR"}]
}
I am trying to join Users with Projects using a lookup aggregation.
db.Projects.aggregate([
{
$lookup: {
from: "Users",
let: { projectAmount: "$budget.amount", projectCurrency: "$budget.currency" },
pipeline: [
{$match: {
$expr: {
$and: [
{ $eq: ["$bids.amount", "$$projectAmount"] },
{ $eq: ["$bids.currency", "$$projectCurrency"] }
]
}
}}
],
as: "matchingBids"
}
]);
But I am always getting the empty result though I have some matching objects in the Users collection. I've gone through the official docs and internet but didn't find anything helpful. Any help would be appreciated. Thank you
Try $in operator with full object and array
pass budget object in let
check $in condition with bids array
db.Projects.aggregate([
{
$lookup: {
from: "Users",
let: { budget: "$budget" },
pipeline: [
{
$match: {
$expr: { $in: ["$$budget", "$bids"] }
}
}
],
as: "matchingBids"
}
}
])
Playground
WARNING:
The above approach will work only when order of fields in object and array of object should be same, below examples will not work!
budget: { amount: 123, currency: "USD" }
bids: [{ currency: "USD", amount: 123 }]
OR
budget: { currency: "USD", amount: 123 }
bids: [{ amount: 123, currency: "USD" }]
EDIT:
After some workaround i found a way to make sure to match exact fields to overcome above situation,
$or condition with both possibility of fields position {amount, currency} and {currency, amount}
db.Projects.aggregate([
{
$lookup: {
from: "Users",
let: { budget: "$budget" },
pipeline: [
{
$match: {
$expr: {
$or: [
{
$in: [
{ amount: "$$budget.amount", currency: "$$budget.currency" },
"$bids"
]
},
{
$in: [
{ currency: "$$budget.currency", amount: "$$budget.amount" },
"$bids"
]
}
]
}
}
}
],
as: "matchingBids"
}
}
])
Playground
Related
I have a collection named users, and this is how one specific user will look like:
{
_id: 'Object ID',
name: 'String',
cart: [
{
product_id: 'Product object ID',
quantity: 'Number',
},
...
],
}
I want my desired results to look like this:
{
_id: 'Object ID',
name: 'String',
cart: [
{
product_id: 'Product object ID',
quantity: 'Number',
product_details: {
'all the details of the product from Products collection which matches the product_id',
},
},
...
],
}
I tried adding addFields into lookup but it's getting too complicated and doesn't work as desired. What's the best way to aggregate this?
You can achieve this in several different ways, here's what I consider to be the most simple:
db.users.aggregate([
{
"$lookup": {
"from": "products",
let: {
cart: "$cart"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$_id",
"$$cart.product_id"
]
}
}
},
{
$replaceRoot: {
newRoot: {
"$mergeObjects": [
"$$ROOT",
{
"$arrayElemAt": [
{
$filter: {
input: "$$cart",
cond: {
$eq: [
"$_id",
"$$this.product_id"
]
}
}
},
0
]
}
]
}
}
}
],
"as": "cart"
}
}
])
Mongo Playground
We are fetching data from applicant and ticket collection. applicant collection has 19000 records and ticket collection has 10000 records.
Please suggest how to improve the performance of the below query. Thanks in advance.
totalData = await applicantObj.aggregate([
{
$match: findApplicantCriteria
},
{
$lookup: {
from: 'tickets',
let: { applicant_id: '$_id' },
pipeline: [
{
$match: {
$expr:
findCriteria
}
}
],
as: 'tickets'
}
},
{
$project: {
_id: 0,
id: "$_id",
name: "$F_First_name",
phoneNumber: "$F_R_Mobile_no",
email: "$F_Email_id",
crn: "$Reg_No",
generatedTickets: { $size: "$tickets" },
}
},
{
$match: { generatedTickets: { "$gt": 0 } }
},
{
$count: "total"
}
])
I'm doing a simple follow friend functionality.
Please see my codes below:
Following schema:
{
"userId": { type: String },
"followers": [{ "followerId": type: String }],
"followings": [{ "followingId": type: String }],
}
User schema:
{
"fullName": { type: String }
}
Note: user 8 has 1 follower and 2 followings.
Now, my expected output should be like this:
"userId": 8,
"followers": [
{
"followerId": 4,
"fullName": "Rose Marriott",
},
{
"followerId": 5,
"fullName": "James Naismith",
}
],
"followings": [
{
"followingId": 1,
"fullName": "Russell Oakham",
},
{
"followingId": 5,
"fullName": "James Naismith",
}
]
This is what I tried so far:
db.followings.aggregate([
{ $unwind: "$followers" },
{
$lookup: {
from: "users",
localField: "followers.followerId",
foreignField: "_id",
as: "users"
}
},
{
$addFields:
{
users: { $arrayElemAt: ["$users", 0] },
},
},
{ $unwind: "$followings" },
{
$lookup: {
from: "users",
localField: "followings.followingId",
foreignField: "_id",
as: "users2"
}
},
{
$addFields:
{
users2: { $arrayElemAt: ["$users2", 0] },
},
},
{ $match: {"userId": mongoose.Types.ObjectId(userId) } },
{
$group: {
_id: "$_id",
userId: { $first: "$userId" },
followers: {
$push: {
followerId: "$followers.followerId",
fullName: "$users.fullName",
}
},
followings: {
$push: {
followingId: "$followings.followingId",
fullName: "$users2.fullName",
}
}
}
}
]);
But I'm getting 2 followers and 2 followings. I wonder what's causing this issue. Appreciate any help. Thanks!
You can try,
$addFields to make a unique array called userIds form both arrays followers and followings, $setUnion to get unique ids,
$lookup with users collection
$project to show fields,
followers get fullName, $map to iterate loop of followers and get the name of followerId from users array using $reduce and $cond
followings get fullName, $map to iterate loop of followings and get the name of followingId from users array using $reduce and $cond
db.followings.aggregate([
{
$addFields: {
userIds: {
$setUnion: [
{
$map: {
input: "$followers",
in: "$$this.followerId"
}
},
{
$map: {
input: "$followings",
in: "$$this.followingId"
}
}
]
}
}
},
{
$lookup: {
from: "users",
localField: "userIds",
foreignField: "_id",
as: "users"
}
},
{
$project: {
userId: 1,
followers: {
$map: {
input: "$followers",
as: "f",
in: {
$mergeObjects: [
"$$f",
{
fullName: {
$reduce: {
input: "$users",
initialValue: "",
in: {
$cond: [
{ $eq: ["$$this._id", "$$f.followerId"] },
"$$this.fullName",
"$$value"
]
}
}
}
}
]
}
}
},
followings: {
$map: {
input: "$followings",
as: "f",
in: {
$mergeObjects: [
"$$f",
{
fullName: {
$reduce: {
input: "$users",
initialValue: "",
in: {
$cond: [
{ $eq: ["$$this._id", "$$f.followingId"] },
"$$this.fullName",
"$$value"
]
}
}
}
}
]
}
}
}
}
}
])
Playground
when working with relations on Mongoose you should create the relationship based on a unique id, and then reference the document. In your case, it would be:
followingSchema = new mongoose.Schema({
{
"followers": [{type: mongoose.Schema.types.ObjectId, ref="User"}],
"followings": [{type: mongoose.Schema.types.ObjectId, ref="User"}],
}
})
userSchema = new mongoose.Schema({
fullname: String,
})
be aware that the userId will be created automatically by Mongoose with a field called _id. so, the end result of creating a new following documents would be:
{
_id: "klajdsfñalkdjf" //random characters created by mongoose,
followers: ["adfadf134234", "adfadte345"] //same as before, these are Ids of the users randomly generated by mongoose
followers: ["adfadf134234", "adfadte345"]
}
{
_id: adfadf134234,
fullName: "alex",
}
now, because there is no use for us to have a random number as information in the fields of following and followers in the following object, we can now use the method .populate() that can be used over the document itself to transform those Ids into actual information. You can see more about it here: mongoose documentation
our final result would be something like this:
{
_id: "añfakdlsfja",
followers : [{_id: "adlfadsfj", fullName: "alex"}],
following : [{_id: "adfadfad" , fullName: "other"}, {_id: "dagadga", fullName: "another"}]
}
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 2 documents: Locations and Orders
Let's say I have 4 locations, and some locations contain orders.
I'm already able to get a list of my locations with the corresponding orders for each location.
What I'm not able to do, is to include the amount of orders for each location, and the total expanses. (Each individual order already contain this information)
Here's Mongo Playground with my current code.
Notice that I want to populate "amount" and "total".
https://mongoplayground.net/p/FO_nYDOD1kn
This is what I use to aggregate my data:
db.Locations.aggregate([{
$lookup: {
from: "Orders",
let: {
loc_id: "$_id"
},
pipeline: [{
$match: {
$expr: {
$eq: ["$$loc_id", "$location"]
}
}
}, {
$project: {
_id: 0,
products: 1
}
}],
as: "orders"
}
}, {
$project: {
_id: 1,
name: 1,
orders: {
total: "insert total orders expanses in this order",
amount: "insert amount of orders for this location",
orders: "$orders"
}
}
}])
and the structure of a single order:
{
"_id": "5e17ab585eb7f11a971bce5c",
"location": "5e17a001f1e0220def7a2b5d",
"total": "1000"
}
Alright, I made something work! Yay!
Here's the solution for whoever finds this.
Location
.aggregate([
{
$lookup: {
from: "orders",
let: { loc_id: "$_id" },
pipeline: [{ $match: { $expr: { $eq: ["$$loc_id", "$location"] } } }],
as: "order"
}
}
,
{
$project: {
_id: 1,
name: 1,
address: 1,
orders: {
total: { $sum: "$order.total" },
amount: { $size: "$order" },
items: "$order"
}
}
}
])
.then(locations => {
res.status(200).json({
success: true,
locations
})
})
.catch(error => {
res.status(500).json({
success: false,
error
})
})