Update nested object in array MongoDB - node.js

I need to find and update documents with category that corresponding to the query. Array could contain mo than one corresponding id.
Query:
{
"ids": ["61f1cda47018c60012b3dd01", "61f1cdb87018c60012b3dd07"],
"userId": "61eab3e57018c60012b3db3f"
}
I got collection with documents like:
`{
"_id":{"$oid":"61f1cdd07018c60012b3dd09"},
"expenses":[
{"category":"61eafc104b88e154caa58616","price":"1111.00"},
{"category":"61f1cdb87018c60012b3dd07","price":"2222.00"},
{"category":"61f1cda47018c60012b3dd01","price":"1241.00"},
{"category":"61f1cdb87018c60012b3dd07","price":"111.00"}
],
"userId":"61eab3e57018c60012b3db3f"
}`
my method:
async myMethod(ids: [string], userId: string) {
try {
const { ok } = await this.ExpensesModel.updateMany(
{"userId": userId, "expenses.category": { $in: ids }},
{$set: {"expenses.$.category": "newCategoryID"}}
);
return ok
} ........
I path array of ids ["61f1cda47018c60012b3dd01","61f1cdb87018c60012b3dd07","61f1cdb87018c60012b3dd07"] and userId, this code update only 1 category by document.
So can i made it with mongo build in methods? or i need to find matching document and update it it by my self and after that update or insert;

Update with arrayFilters
db.collection.update({
"expenses.category": {
$in: [
"61f1cda47018c60012b3dd01",
"61f1cdb87018c60012b3dd07"
]
}
},
{
$set: {
"expenses.$[elem].category": "61eab3e57018c60012b3db3f"
}
},
{
arrayFilters: [
{
"elem.category": {
$in: [
"61f1cda47018c60012b3dd01",
"61f1cdb87018c60012b3dd07"
]
}
}
]
})
mongoplayground

Related

MongoDB: Access current value of document when adding element to array via $push

I have collection MyCollection which basically consists of its _id and a string called comment.
This collection should be bulk-updatable.
That's done like this:
for (const obj of inputObjects) {
bulkObjectsToWrite.push({
updateOne: {
filter: { _id: obj._id },
update: {
$set: {
comment: obj.comment
}
}
}
})
}
await MyCollection.bulkWrite(bulkObjectsToWrite)
So far so good.
However, now the requirement is, that a commentHistory should be added which should look like [{"oldValue": "oldValueOfComment", "newValue": "newValueOfComment"}, ...]
I know I need to use $push for adding a new object to the commentHistory array. But how do I access the comment of the document updated right now, i.e. its current value?
I've tried
$push: {
commentHistory: {
newValue: obj.comment,
oldValue: '$comment',
},
},
but to no avail. The string $comment is added hard-coded, instead of the field being accessed.
(Using Mongoose 5.12.10 and Mongo 4.4.18)
You need to use update with aggregate pipeline.
db.collection.update({
"key": 1
},
[
{
$set: {
"comment": "New",
"commentHistory": {
"$concatArrays": [ //concatenate existing history array with new array entry
"$commentHistory",
[
{
"newValue": "New",
"oldValue": "$comment" //referencing the existing value
}
]
]
}
}
}
])
Demo

Push to array in Mongodb

How do I push to the inner arrays in MongoDB?
[
{ emoji1: ['user19', 'user20', 'user21']},
{ emoji5: ['user12', 'user13', 'user14']},
{ emoji9: ['user29', 'user30', 'user34']}
]
I tried:
await Post
.updateOne({ _id: postID }, {
$push: {
[`reactions[0].emoji1`]: 'random user'
}
})
...where Post is my Mongoose Schema and "reactions" is the array above. I think I am doing something wrong in $push.
The result should be:
[
{ emoji1: ['user19', 'user20', 'user21', 'random user']},
{ emoji5: ['user12', 'user13', 'user14']},
{ emoji9: ['user29', 'user30', 'user34']}
]
You can use this query:
db.collection.update({
id: 1,
"reactions.emoji1": {
"$exists": true
}
},
{
"$push": {
"reactions.$.emoji1": "random user"
}
})
Example here

how to display cart items in cart pages with node js

this is the code which i used
getCartProducts:(userId)=>{
return new Promise(async(resolve,reject)=>{
let cartItems = await db.get().collection(collection.CART_COLLECTION).aggregate([
{
$match:{user:objectId(userId)}
},
{
$lookup:{
from:collection.PRODUCT_COLLECTION,
let:{prodList:'$products'},
pipeline:[
{
$match:{
$expr:{
$in:['$_id',"$$prodList"]
},
},
},
],
as:'cartItems'
}
}
]).toArray()
resolve(cartItems)
})
}
router code:-
router.get('/cart',varifiedLogin,async(req,res)=>{
let products = await userHelpers.getCartProducts(req.session.user._id)
console.log(products);
res.render('user/cart')
})
output i am getting:-
[
{
_id: 5fac03eed5f7b103a8906c03,
user: 5fa8f0dbd417a11f6db37544,
products: [
5fac03eed5f7b103a8906c02,
5fac06f454045e0403e470e0,
5fac268d2071f006ff398666
],
cartItems: []
}
]
this is the expected output. i should be getting objects inside the cartItems.
_id is the product id
user field having the user id
the product having 3 items which i added to the cart.
and inside cart item i need to get the products as array.
[
{
_id: 5fac03eed5f7b103a8906c03,
user: 5fa8f0dbd417a11f6db37544,
products: [
5fac03eed5f7b103a8906c02,
5fac06f454045e0403e470e0,
5fac268d2071f006ff398666
],
cartItems: [[Object],[Object]]
}
]
Use an array of second Argument in $in. $in takes exactly 2 arguments.
like this-
$in: ['$_id', ["$$prodList"]]

Mongoose findbyIdAndUpdate - how to update one field using sum from another array field

So this is how my document looks like
"donator": [
{
"_id": "5edbd7d182af1f5aceab62bb",
"donatorName": "Niki",
"donationValue": 5000000
},
{
"_id": "5edbd7d182af1f5aceab62bc",
"donatorName": "Brian",
"donationValue": 5000000
}
],
"currentValue" : 1000000
I get my currentValue from donationValue sum in donator array :
donator.reduce((a, { donationValue }) => a + donationValue,0);
and I want to update the document by adding new object in donator array, and automatically updates currentValue field when the data updated.
I tried using aggregate
MyCollection.findByIdAndUpdate(id, [
{
$set: req.body,
$set: { currentValue: { $sum: { $sum: donator.donationValue } } },
},
]);
It's a good practice to sanitise your input and not directly use req.body.
so let's say
const donator = sanitizeDonator(req.body)
Then you can add donator and update currentValue as follows.
Note that _id on donator won't be auto generated by the update query, so it has to be present in the donator object.
MyCollection.findByIdAndUpdate(id, [
{
$set: {
donator: {
$concatArrays: ["$donator", [donator]] // adding donator to the last element
}
}
}, {
$set: {
currentValue: {
$reduce: {
initialValue: 0,
input: "$donator",
in: {
$add: ["$$this.donationValue", "$$value"]
}
}
}
}
}
]);
Relevant APIs
$concatArrays,
$reduce

How to pass an optional argument in Mongoose/MongoDb

I have the following query:
Documents.find({
$and: [
{
user_id: {$nin:
myUserId
}
},
{ date: { $gte: dateMax, $lt: dateMin } },
{documentTags: {$all: tags}}
],
})
What I'm trying to do is make the documentTags portion of the query optional. I have tried building the query as follows:
let tags = " ";
if (req.body.tags) {
tags = {videoTags: {$all: req.body.tags}};
}
let query = {
$and: [
{
user_id: {$nin:
myUserId
}
},
{ date: { $gte: dateMax, $lt: dateMin } },
tags
],
}
and then Document.find(query). The problem is no matter how I modify tags (whether undefined, as whitespace, or otherwise) I get various errors like $or/$and/$nor entries need to be full objects and TypeError: Cannot read property 'hasOwnProperty' of undefined.
Is there a way to build an optional requirement into the query?
I tried the option below and the query is just returning everything that matches the other fields. For some reason it isn't filtering by tags. I did a console.log(queryArr) and console.log(query) get the following respectively:
[
{ user_id: { '$nin': [Array] } },
{
date: {
'$gte': 1985-01-01T00:00:00.000Z,
'$lt': 2020-01-01T00:00:00.000Z
}
},
push: { documentTags: { '$all': [Array] } }
]
console.log(query)
{
'$and': [
{ user_id: [Object] },
{ date: [Object] },
push: { documentTags: [Object] }
]
}
You are almost there. Instead you could construct the object outside the query and just put the constructed query in $and when done..
let queryArr = [
{
user_id: {$nin: myUserId}
},
{ date: { $gte: dateMax, $lt: dateMin } }
];
if (req.body.tags) {
queryArr.push({videoTags: {$all: req.body.tags}});
}
let query = {
$and: queryArr
}
Now you can control the query by just pushing object into the query Array itself.
I figured out why it wasn't working. Basically, when you do myVar.push it creates a key-value pair such as [1,2,3,push:value]. This would work if you needed to append a k-v pair in that format, but you'll have difficulty using it in a query like mine. The right way for me turned out to be to use concact which appends the array with just the value that you set, rather than a k-v pair.
if (req.body.tags){
queryArgs = queryArgs.concat({documentTags: {$all: tags}});
}
let query = {
$and: queryArgs
}

Resources