Combining $lookup aggregation inside updateMany? - node.js

I have a collection of users like this
[
{ _id: ObjectId("61a6d586e56ea12d6b63b68e"), fullName: "Mr A" },
{ _id: ObjectId("6231a89b009d3a86c788bf39"), fullName: "Mr B" },
{ _id: ObjectId("6231a89b009d3a86c788bf3a"), fullName: "Mr C" }
]
And a collection of complains like this
[
{ _id: ObjectId("6231aaba2a038b39d992099b"), type: "fee", postedBy: ObjectId("61a6d586e56ea12d6b63b68e" },
{ _id: ObjectId("6231aaba2a038b39d992099b"), type: "fee", postedBy: ObjectId("6231a89b009d3a86c788bf3c" },
{ _id: ObjectId("6231aaba2a038b39d992099b"), type: "fee", postedBy: ObjectId("6231a89b009d3a86c788bf3b" },
]
I want to check if the postedBy fields of complains are not existed in users, then update by using the updateMany query
By the way, I have an optional way to achieve the goal but must use 2 steps:
const complains = await Complain.aggregate()
.lookup({
from: "users",
localField: "postedBy",
foreignField: "_id",
as: "postedBy",
})
.match({
$expr: {
$eq: [{ $size: "$postedBy" }, 0],
},
});
complains.forEach(async (complain) => {
complain.type = "other";
await complain.save();
});
Therefore, can I combine 2 steps into a single updateMany query? Like $match and $lookup inside updateMany query?

With MongoDB v4.2+, you can use $merge to perform update at last stage of aggregation.
db.complains.aggregate([
{
"$lookup": {
from: "users",
localField: "postedBy",
foreignField: "_id",
as: "postedByLookup"
}
},
{
$match: {
postedByLookup: []
}
},
{
"$addFields": {
"type": "other"
}
},
{
"$project": {
postedByLookup: false
}
},
{
"$merge": {
"into": "complains",
"on": "_id",
"whenMatched": "replace"
}
}
])
Here is the Mongo playground for your reference.

Related

How to get the first element from a child lookup in aggregation - Mongoose

I'm trying to find all the docs from groupUserRoleSchema with a specific $match condition in the child. I'm getting the expected result, but the child application inside groupSchema is coming as an array.
I just need the first element from the application array as an object. How to convert this application into a single object.
These are my models
const groupUserRoleSchema = new mongoose.Schema({
group: {
type: mongoose.Schema.Types.ObjectId,
ref: 'group'
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
}
});
const groupSchema = new mongoose.Schema({
application: {
type: mongoose.Schema.Types.ObjectId,
ref: 'application'
}
});
Here is my aggregate condition.
groupUserRoleModel.aggregate([
{
$lookup: {
from: "groups", //must be PHYSICAL collection name
localField: "group",
foreignField: "_id",
as: "group",
}
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "user",
}
},
{
$lookup: {
from: "applications",
localField: "group.application",
foreignField: "_id",
as: "group.application",
}
},
{
$addFields: {
group: {
$arrayElemAt: ["$group", 0],
},
user: {
$arrayElemAt: ["$user", 0],
}
},
},
{
$match: {
"user.email_id": requestHeaders.user_email
},
}
]);
Here the $lookup group.application is coming as an array. Instead i need it as an object.
Below added is the current output screen-shot
Here is the expected output screen-shot
Any suggestions ?
A alternative is re-writing the main object return, and merge the fields.
The lookup field, gonna use the $arrayElemAt attribute at position 0, with the combination of the root element '$$ROOT'.
const result = await groupUserRoleModel.aggregate([
{
$match: {
"user.email_id": requestHeaders.user_email //The Match Query
},
},
{
$lookup: {
from: 'groups', // The collection name
localField: 'group',
foreignField: '_id',
as: 'group', // Gonna be a group
},
},
{
// Lookup returns a array, so get the first once it is a _id search
$replaceRoot: {
newRoot: {
$mergeObjects: [ // merge the object
'$$ROOT',// Get base object
{
store: {
$arrayElemAt: ['$group', 0], //Get first elemnt
},
},
],
},
},
},
]);

MongoDB Aggregate - How to implement match and lookup

I need to get the data from two collections (expenses, accounts). The data must be between the given date range and amount range.
date, amount, currency, type is in Expenses collection and accountId is in account collection as _id.
I tried the following query but it's not returning anything.
const response = await Expense.aggregate( [
{ $match: {
$and: [
{
currency: "1",
expenseType: "1"
},
{
date: {
$gte: new Date(date.value.from) , $lte: new Date(date.value.to)
},
amount: {
$gt: parseFloat(amount.value.from) , $lt: parseFloat(amount.value.to)
}
}
],
} },
{
$lookup: {
from: "accounts",
localField: "accountId",
foreignField: "_id",
as: "account"
}
}
] )
This is the schema of expenses collection:
Document of expense collection:
Document of account collection:
From the attached expense document, the amount field was a String type but not a Number type.
Data
amount: "20000"
Schema
amount: {
type: Number,
required: true
}
You should revise your Expense document in MongoDB with your schema.
From your Aggregation Query, you need to convert amount from String to Decimal to compare.
Solution 1: With $expr
db.expense.aggregate([
{
$match: {
$and: [
{
currency: "1",
expenseType: "1"
},
{
date: {
$gte: new Date(date.value.from),
$lte: new Date(date.value.to)
},
},
{
$expr: {
$and: [
{
$gt: [
{
"$toDecimal": "$amount"
},
parseFloat(amount.value.from)
]
},
{
$lt: [
{
"$toDecimal": "$amount"
},
parseFloat(amount.value.to)
]
}
]
}
}
],
}
},
{
$lookup: {
from: "accounts",
localField: "accountId",
foreignField: "_id",
as: "account"
}
}
])
Solution 1 on Mongo Playground
Solution 2: Add $set as first stage
db.expense.aggregate([
{
$set: {
amount: {
"$toDecimal": "$amount"
}
}
},
{
$match: {
$and: [
{
currency: "1",
expenseType: "1"
},
{
date: {
$gte: new Date(date.value.from),
$lte: new Date(date.value.to)
},
amount: {
$gt: parseFloat(amount.value.from),
$lt: parseFloat(amount.value.to)
}
}
]
}
},
{
$lookup: {
from: "accounts",
localField: "accountId",
foreignField: "_id",
as: "account"
}
}
])
Solution 2 on Mongo Playground

aggregate multiple collections with same field mongo

I want to aggregate Collection A with Collection B and C.
Collection A's _id is saved in collection B and C as ret_id.
I tried following:
await col_A.aggregate([
{
$lookup: {
from: "col_B",
localField: "_id",
foreignField: "ret_id",
as: "task",
},
},
{
$lookup: {
from: "col_C",
localField: "_id",
foreignField: "ret_id",
as: "task",
},
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
tasks: { $push: "$task" },
},
},
])
But like that it shows nothing. I also tried to put two groups, but than I wasn't able to have the output in only one array.
So my desired output is like that:
{
_id: id,
name: name,
tasks: [
{
_id: id,
task_name: task_name
ret_id: ret_id,
// from collection B
},
{
_id: id,
task_name: task_name
ret_id: ret_id
// from collection C
},
]
}

how too use $lookup in mongoose to fetch array object

I have the following aggregation and It does not return the user profiles properly
const newConverSation = await Messenger.Messenger.aggregate([
{ $match: {
users: mongoose.Types.ObjectId("6084036ad4d4cd40a47afba4")}},
{ $sort: { updatedAt: -1 } },
{
$group: {
_id: { $setUnion: "$users" },
message: { $first: "$$ROOT" }
}
},
{
$lookup: {
from: 'users',
localField: 'users',
foreignField: '_id',
as: 'users'
}
}
])
In the outcome, there is an users array like this
"users" : [
ObjectId("60841d03f6ccad2b0400f619"),
ObjectId("6084036ad4d4cd40a47afba4")
],
and I just want to fetch user profiles depending on these two Id's but it does not return profiles in the current way.
Try adding the .populate after the .find property where you want to show this data.

Mongoose join two collections and get only specific fields from the joined collection

I have a problem joining two collections in mongoose. I have two collections namely: student and exams.
Student model:
{
fullname: { type: String, required: true },
email: { type: String, required: true },
}
Exams model:
{
test: { type: String, required: false },
top10: [
type: {
studentId: { type: String, required: true },
score: { type: Number, required: false },
}
]
}
Now, I want to join them two by studentId. The result should be:
{
"test": "Sample Test #1",
"students": [
{
"studentId": "5f22ef443f17d8235332bbbe",
"fullname": "John Smith",
"score": 11
},
{
"studentId": "5f281ad0838c6885856b6c01",
"fullname": "Erlanie Jones",
"score": 9
},
{
"studentId": "5f64add93dc79c0d534a51d0",
"fullname": "Krishna Kumar",
"score": 5
}
]
}
What I did was to use aggregate:
return await Exams.aggregate([
{$lookup:
{
from: 'students',
localField: 'top10.studentId',
foreignField: '_id',
as: 'students'
}
}
]);
But this result is not what I had hoped it should be. Any ideas how to achieve this? I would gladly appreciate any help. Thanks!
You can try,
$lookup with students collection
$project to show required fields, $map to iterate loop of top10 array and inside use $reduce to get fullname from students and merge with top10 object using $mergeObjects
db.exams.aggregate([
{
$lookup: {
from: "students",
localField: "top10.studentId",
foreignField: "_id",
as: "students"
}
},
{
$project: {
test: 1,
students: {
$map: {
input: "$top10",
as: "top10",
in: {
$mergeObjects: [
"$$top10",
{
fullname: {
$reduce: {
input: "$students",
initialValue: 0,
in: {
$cond: [
{ $eq: ["$$this._id", "$$top10.studentId"] },
"$$this.fullname",
"$$value"
]
}
}
}
}
]
}
}
}
}
}
])
Playground
Second option you can use $unwind before $lookup,
$unwind deconstruct top10 array
$lookup with students collection
$addFields to convert students array to object using $arrayElemtAt
$group by _id and construct students array and push required fields
db.exams.aggregate([
{ $unwind: "$top10" },
{
$lookup: {
from: "students",
localField: "top10.studentId",
foreignField: "_id",
as: "students"
}
},
{ $addFields: { students: { $arrayElemAt: ["$students", 0] } } },
{
$group: {
_id: "$_id",
test: { $first: "$test" },
students: {
$push: {
studentId: "$top10.studentId",
score: "$top10.score",
fullname: "$students.fullname"
}
}
}
}
])
Playground

Resources