I am using the following query to update the documents in mongodb but it throws me the error The dollar ($) prefixed field '$subtract' in 'abc.$subtract' is not valid for storage.
const bulkUpdate = arrs.map(arr => {
const { val = 0 } = arr
return {
updateMany: {
filter: {
date: 20201010
},
update: {
$set: {
abc: {
$subtract: [val, { $add: [{ $ifNull: ['$a1', 0] }, { $ifNull: ['$b1', 0] } ] }]
}
},
},
},
}
})
if (bulkUpdate.length > 0) {
return mongoConnection.pool
.db('test')
.collection('testTable')
.bulkWrite(bulkUpdate)
}
Thanks is advance
$subtract and $ifNull are not update operators, hence you can't use them within an update (except a within a pipelined update).
If you're using Mongo version 4.2+ you can use use a pipeline update instead of a "document update" like so:
{
updateMany: {
filter: {
date: 20201010
},
update: [
{
$set: {
abc: {
$subtract: [val, {$add: [{$ifNull: ['$a1', 0]}, {$ifNull: ['$b1', 0]}]}]
}
}
}
]
}
}
If you aren't then you'll have to read each document and update it sepratley as prior to version 4.2 you could not access document fields while updating as you are trying to do.
Related
I am using the mongo change stream to update some of my data in the cache. This is the Documentation i am following.
I want to know when one of the keys in my object gets updated.
Schema :- attributes: { position: { type: Number } },
How will I come to know when the position will get updated?
This is how my updateDescription object looks like after updation
updateDescription = {
updatedFields: { 'attributes.position': 1, randomKey: '1234'},
removedFields: []
}
Tried this and its not working
Collection.watch(
[
{
$match: {
$and: [
{ operationType: { $in: ['update'] } },
{
$or: [
{ 'updateDescription.updatedFields[attributes.position]': { $exists: true } },
],
},
],
},
},
],
{ fullDocument: 'updateLookup' },
);
I was able to fix it like this,
const streamUpdates = <CollectionName>.watch(
[
{
$project:
{
'updateDescription.updatedFields': { $objectToArray: '$updateDescription.updatedFields' },
fullDocument: 1,
operationType: 1,
},
},
{
$match: {
$and: [
{ operationType: 'update' },
{
'updateDescription.updatedFields.k': {
$in: [
'attributes.position',
],
},
},
],
},
},
],
{ fullDocument: 'updateLookup' },
);
and then do something with the stream
streamUpdates.on('change', async (next) => {
//your logic
});
Read more here MongoDB
When using $mergeObjects in update pipeline, the original object is being
overwritten. Are there any alternatives to avoid this from happening or can $mergeObjects be used in any way to avoid this.
I have a document as follows:
{
"employment": {
"selfEmployed": {
"netProfit": 1000,
"addbacks": [
{
"addbackId": "5a934e000102030405000001",
"value": 1000,
}
]
}
}
}
I have update data as follows:
const update = {
"selfEmployed": {
"netProfit": 22,
}
};
The update query is:
db.collection.update({},
[
{
$set: {
"employment": {
$mergeObjects: [
"$employment",
update
]
}
}
}
])
However $mergeObjects overwrites the object to give following result:
{
"employment": {
"selfEmployed": {
"netProfit": 22
}
}
}
Expected Result:
{
"employment": {
"selfEmployed": {
"netProfit": 22,
"addbacks": [
{
"addbackId": "5a934e000102030405000001",
"value": 1000,
}
]
}
}
}
Playground
Edit 1:
I can only rely on top level field to perform update.
Some answers below do work for selfEmployed.
But I have other fields on the collection as well.
e.g.
{
"employment": {
"selfEmployed": {
"netProfit": 1000,
"addbacks": [
{
"addbackId": "5a934e000102030405000001",
"value": 1000,
}
]
},
"unemployed": {
"reason": "some reason",
//... other data
}
// .. other data
}
}
And i don't want to repeat or write multiple queries for updating nested fields and using direct set is out of the question as well.
I'm looking for a way using the update pipeline as I have given above.
According to $mergeObjects Behaviour,
$mergeObjects overwrites the field values as it merges the documents. If documents to merge include the same field name, the field, in the resulting document, has the value from the last document merged for the field.
Hence, the original employment.selfEmployed will be replaced with the new value.
Solution 1: Direct $set value for employment.selfEmployed.netProfit.
db.collection.update({},
[
{
$set: {
"employment.selfEmployed.netProfit": 22
}
}
])
Sample Mongo Playground for Solution 1
Solution 2: $mergeObjects for employment.selfEmployed only.
db.collection.update({},
[
{
$set: {
"employment.selfEmployed": {
$mergeObjects: [
"$employment.selfEmployed",
{
"netProfit": 22
}
]
}
}
}
])
Sample Mongo Playground for Solution 2
I have the following documents:
[{
"_id":1,
"name":"john",
"position":1
},
{"_id":2,
"name":"bob",
"position":2
},
{"_id":3,
"name":"tom",
"position":3
}]
In the UI a user can change position of items(eg moving Bob to first position, john gets position 2, tom - position 3).
Is there any way to update all positions in all documents at once?
You can not update two documents at once with a MongoDB query. You will always have to do that in two queries. You can of course set a value of a field to the same value, or increment with the same number, but you can not do two distinct updates in MongoDB with the same query.
You can use db.collection.bulkWrite() to perform multiple operations in bulk. It has been available since 3.2.
It is possible to perform operations out of order to increase performance.
From mongodb 4.2 you can do using pipeline in update using $set operator
there are many ways possible now due to many operators in aggregation pipeline though I am providing one of them
exports.updateDisplayOrder = async keyValPairArr => {
try {
let data = await ContestModel.collection.update(
{ _id: { $in: keyValPairArr.map(o => o.id) } },
[{
$set: {
displayOrder: {
$let: {
vars: { obj: { $arrayElemAt: [{ $filter: { input: keyValPairArr, as: "kvpa", cond: { $eq: ["$$kvpa.id", "$_id"] } } }, 0] } },
in:"$$obj.displayOrder"
}
}
}
}],
{ runValidators: true, multi: true }
)
return data;
} catch (error) {
throw error;
}
}
example key val pair is: [{"id":"5e7643d436963c21f14582ee","displayOrder":9}, {"id":"5e7643e736963c21f14582ef","displayOrder":4}]
Since MongoDB 4.2 update can accept aggregation pipeline as second argument, allowing modification of multiple documents based on their data.
See https://docs.mongodb.com/manual/reference/method/db.collection.update/#modify-a-field-using-the-values-of-the-other-fields-in-the-document
Excerpt from documentation:
Modify a Field Using the Values of the Other Fields in the Document
Create a members collection with the following documents:
db.members.insertMany([
{ "_id" : 1, "member" : "abc123", "status" : "A", "points" : 2, "misc1" : "note to self: confirm status", "misc2" : "Need to activate", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") },
{ "_id" : 2, "member" : "xyz123", "status" : "A", "points" : 60, "misc1" : "reminder: ping me at 100pts", "misc2" : "Some random comment", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") }
])
Assume that instead of separate misc1 and misc2 fields, you want to gather these into a new comments field. The following update operation uses an aggregation pipeline to:
add the new comments field and set the lastUpdate field.
remove the misc1 and misc2 fields for all documents in the collection.
db.members.update(
{ },
[
{ $set: { status: "Modified", comments: [ "$misc1", "$misc2" ], lastUpdate: "$$NOW" } },
{ $unset: [ "misc1", "misc2" ] }
],
{ multi: true }
)
Suppose after updating your position your array will looks like
const objectToUpdate = [{
"_id":1,
"name":"john",
"position":2
},
{
"_id":2,
"name":"bob",
"position":1
},
{
"_id":3,
"name":"tom",
"position":3
}].map( eachObj => {
return {
updateOne: {
filter: { _id: eachObj._id },
update: { name: eachObj.name, position: eachObj.position }
}
}
})
YourModelName.bulkWrite(objectToUpdate,
{ ordered: false }
).then((result) => {
console.log(result);
}).catch(err=>{
console.log(err.result.result.writeErrors[0].err.op.q);
})
It will update all position with different value.
Note : I have used here ordered : false for better performance.
Suppose I have the following document:
{
_id: "609bcd3160653e022a6d0fd8",
companies:{
apple: [
{
product_id: "609bcd3160653e022a6d0fd7"
},
{
product_id: "609bcd3160653e022a6d0fd6"
}
],
microsoft: [
{
product_id: "609bcd3160653e022a6d0fd5"
},
{
product_id: "609bcd3160653e022a6d0fd4"
}
]
}
}
How can I find and delete the object with the product_id: "609bcd3160653e022a6d0fd4".
P.S: I use MongoDB native driver for Nodejs.
Thanks a lot!
If I don't know the name of the array? I want to find the object by product_id
It is not possible with regular update query, you can try update with aggregation pipeline starting from MongoDB 4.2,
$objectToArray convert companies object to array in key-value format
$map to iterate loop of above converted array
$filter to filter dynamic array's by product_id
$arrayToObject convert key-value array to regular object format
let product_id = "609bcd3160653e022a6d0fd4";
YourSchema.findOneAndUpdate(
{ _id: "609bcd3160653e022a6d0fd8" }, // put your query
[{
$set: {
companies: {
$arrayToObject: {
$map: {
input: { $objectToArray: "$companies" },
in: {
k: "$$this.k",
v: {
$filter: {
input: "$$this.v",
cond: { $ne: ["$$this.product_id", product_id] }
}
}
}
}
}
}
}
}],
{
new: true // return updated document
}
);
Playground
I want to calculate the average in .js file and insert the result of all documents inside a collection in Mongo DB, but code below doesn't work.
db.createCollection("myCol");
db.myCol.insert( { item: "card", qty: 15 } );
db.myCol.insert( { item: "card", qty: 92 } );
var mean = db.myCol.aggregate(
[
{
$group:
{
_id: "$card",
average: { $avg: "$qty" }
}
}
]
);
db.myCol.updateMany(
{ "item": { $eq: "card" } },
{
$set: { "mean" : mean },
}
)
I run .js files in Mongo Shell with command
load (jsfile.js)
db.createCollection("myCol");
db.myCol.insert( { item: "card", qty: 15 } );
db.myCol.insert( { item: "card", qty: 92 } );
var mean = db.myCol.aggregate(
[
{
$group:
{
_id: "$item",
average: { $avg: "$qty" }
}
}
]
).toArray();
db.myCol.updateMany(
{ "item": { $eq: "card" } },
{
$set: { "mean" : mean },
}
)
you should try this one. It will work.
Problem
There are few operations which return cursor objects. In your case, the cursor object for aggregation query was storing at mean variable. That's the reason you were not getting your desired value instead a cursor object was getting updated in myCol collection.
Solution
I have updated the code with toArray method, which converts cursor to an array of objects.So this will work for sure.
let me know if its help you.