MongoDb - Equivalent for traditional update-set-where - mongodb

Does mongo have an equivalent for
update emp
set sal = sal * 1.20
where empno in (1,2,3);
Note that I want the matched records sal.
db.users.update(
{ empno: { $in: [1,2,3]} },
{ $set: { sal: $matched.sal * 1.20 } }, # Not real syntax
{ multi: true }
)
I have looked through the documentation but couldn't find anything. I can do it with find-save but I am dealing with a large collection and multi update will be a more desirable solution.

In mongo shell there is no such function as multiply element. There are few field update operators, but in your situation you need to run a custom forEach script:
db.users.find({ empno: { $in: [1,2,3]} }).forEach(function(e) {
e.sal = e.sal * 1.2;
db.users.save(e);
});

Unfortunately, this is not possible with MongoDB. The closes you could get is with $where operator, but reference specifically warns against updating documents from $where. So, yes, you will have to fetch documents one by one and perform updates individually.

Related

MongoDB: Remove certain number of elements from start of an array

Is there a way to remove certain amounts of elements from start of an array in MongoDB?
Suppose I don't know about element's details (like id or uuid) but I know that I want to remove the first N elements from the start of it. Is there a way to do it in mongoDB? I know I can fetch the whole document and process it in my own programming language environment but I thought it would be nicer if mongoDB already implemented a way to achieve it atomically by its own query language.
There is a $pop operator to remove a single element from the array either from top or bottom position,
and there is a closed jira support request SERVER-4798 regarding multiple pop operations, but in comment they have suggested to use update with aggregation pipeline option.
So you can try update with aggregation pipeline starting from MongoDB 4.2,
$slice, pass negative number and it will slice elements from 0 index
let n = 2;
db.collection.updateOne(
{}, // your query
[{
$set: { arr: { $slice: ["$arr", -n] } }
}]
)
Playground
What #turivishal metioned is true however it only works if array's size is always 4. For it to work for all array sizes we should consider size of the array in aggregation, so:
let n = 2;
db.collection.update({},
[
{
$set: {
arr: {
$slice: [
"$arr",
{
$subtract: [
-n,
{
$size: "$arr"
}
]
},
]
}
}
}
])
Playground

Querying two array fields and getting positional result

I've been trying to do important update operations using two-phase commit method. Unfortunately, the field that will be updated in an array. But the same document have to has a pendingTransactions field to store the current transactions. And according to this documentation, MongoDB doesn't support positional update
if two array fields are in the query document.
Is there any chance to solve this situation?
Other Additional informations and 'Correct Results' using Two Array Fields In The Query
Actually I used a second array in query document without knowing the problem. It works stable. When testing, I encountered with the problem at another update operation. So, the first query response correctly at every time.
the stable part is like the below. With $ne operator it works correct;
invoices and pendingTransactions are Array fields
var query = {
_id: customer_id,
invoices: { $elemMatch: { 'year': 2015, 'month': 8 } },
pendingTransactions: { $ne: transaction_id }
}
Customers.findOne(filter, { 'invoices.$' }, function(err, customer){
/* Positional operator $ works correct */
}
the unstable part is like the below;
var query = {
_id: customer_id,
invoices: { $elemMatch: { 'year': 2015, 'month': 8 } },
pendingTransactions: transaction_id
}
Customers.findOne(filter, { 'invoices.$' }, function(err, customer){
/* Positional operator $ doesn't work correct */
}

Can mongodb do this SQL?

In MySQL Query:
UPDATE $TABLE SET `col1`=`col2` WHERE 1;
UPDATE $TABLE SET `col1`=IF(`col1`>100, `col1`-100, `col1`) WHERE 1;
Can MongoDB do like this?
What you appear to want to do is a conditional update - update the document based on its current state. MongoDB only has very limited support for this. See the update operators for details.
For the first query, it would be best to retrieve the documents, change them, and then save them. Unless your intention is not to copy the field but to rename it. Then you can use $rename.
For the second, it would be possible with this query:
db.collection.update(
{ field1: { $gt: 100} },
{ $inc: { field1, -100 },
{ multi: true}
);
By the way: You can also use the same pattern in SQL. It would likely work faster than using an if-condition.
UPDATE $TABLE SET `col1`=`col1`-100 WHERE `col1 ` > 100

Update with expression instead of value

I am totally new to MongoDB... I am missing a "newbie" tag, so the experts would not have to see this question.
I am trying to update all documents in a collection using an expression. The query I was expecting to solve this was:
db.QUESTIONS.update({}, { $set: { i_pp : i_up * 100 - i_down * 20 } }, false, true);
That, however, results in the following error message:
ReferenceError: i_up is not defined (shell):1
At the same time, the database did not have any problem with eating this one:
db.QUESTIONS.update({}, { $set: { i_pp : 0 } }, false, true);
Do I have to do this one document at a time or something? That just seems excessively complicated.
Update
Thank you Sergio Tulentsev for telling me that it does not work. Now, I am really struggling with how to do this. I offer 500 Profit Points to the helpful soul, who can write this in a way that MongoDB understands. If you register on our forum I can add the Profit Points to your account there.
I just came across this while searching for the MongoDB equivalent of SQL like this:
update t
set c1 = c2
where ...
Sergio is correct that you can't reference another property as a value in a straight update. However, db.c.find(...) returns a cursor and that cursor has a forEach method:
Queries to MongoDB return a cursor, which can be iterated to retrieve
results. The exact way to query will vary with language driver.
Details below focus on queries from the MongoDB shell (i.e. the
mongo process).
The shell find() method returns a cursor object which we can then iterate to retrieve specific documents from the result. We use
hasNext() and next() methods for this purpose.
for( var c = db.parts.find(); c.hasNext(); ) {
print( c.next());
}
Additionally in the shell, forEach() may be used with a cursor:
db.users.find().forEach( function(u) { print("user: " + u.name); } );
So you can say things like this:
db.QUESTIONS.find({}, {_id: true, i_up: true, i_down: true}).forEach(function(q) {
db.QUESTIONS.update(
{ _id: q._id },
{ $set: { i_pp: q.i_up * 100 - q.i_down * 20 } }
);
});
to update them one at a time without leaving MongoDB.
If you're using a driver to connect to MongoDB then there should be some way to send a string of JavaScript into MongoDB; for example, with the Ruby driver you'd use eval:
connection.eval(%q{
db.QUESTIONS.find({}, {_id: true, i_up: true, i_down: true}).forEach(function(q) {
db.QUESTIONS.update(
{ _id: q._id },
{ $set: { i_pp: q.i_up * 100 - q.i_down * 20 } }
);
});
})
Other languages should be similar.
//the only differnce is to make it look like and aggregation pipeline
db.table.updateMany({}, [{
$set: {
col3:{"$sum":["$col1","$col2"]}
},
}]
)
You can't use expressions in updates. Or, rather, you can't use expressions that depend on fields of the document. Simple self-containing math expressions are fine (e.g. 2 * 2).
If you want to set a new field for all documents that is a function of other fields, you have to loop over them and update manually. Multi-update won't help here.
Rha7 gave a good idea, but the code above is not work without defining a temporary variable.
This sample code produces an approximate calculation of the age (leap years behinds the scene) based on 'birthday' field and inserts the value into suitable field for all documents not containing such:
db.employers.find({age: {$exists: false}}).forEach(function(doc){
var new_age = parseInt((ISODate() - doc.birthday)/(3600*1000*24*365));
db.employers.update({_id: doc._id}, {$set: {age: new_age}});
});
Example to remove "00" from the beginning of a caller id:
db.call_detail_records_201312.find(
{ destination: /^001/ },
{ "destination": true }
).forEach(function(row){
db.call_detail_records_201312.update(
{ _id: row["_id"] },
{ $set: {
destination: row["destination"].replace(/^001/, '1')
}
}
)
});

Multiply field by value in Mongodb

I've been looking for a way to create an update statement that will take an existing numeric field and modify it using an expression. For example, if I have a field called Price, is it possible to do an update that sets Price to 50% off the existing value ?
So, given { Price : 19.99 }
I'd like to do db.collection.update({tag : "refurb"}, {$set {Price : Price * 0.50 }}, false, true);
Can this be done or do I have to read the value back to the client, modify, then update ? I guess the question then is can expressions be used in update, and can they reference the document being updated.
You can run server-side code with db.eval().
db.eval(function() {
db.collection.find({tag : "refurb"}).forEach(function(e) {
e.Price = e.Price * 0.5;
db.collection.save(e);
});
});
Note this will block the DB, so it's better to do find-update operation pair.
See https://docs.mongodb.com/manual/core/server-side-javascript/
In the new Mongo 2.6.x there is a $mul operator. It would multiply the value of the field by the number with the following syntax.
{
$mul: { field: <number> }
}
So in your case you will need to do the following:
db.collection.update(
{ tag : "refurb"},
{ $mul: { Price : 0.5 } }
);
Starting Mongo 4.2, db.collection.update() can accept an aggregation pipeline, finally allowing the update of a field based on another field:
// { price: 19.99 }
// { price: 2.04 }
db.collection.update(
{},
[{ $set: { price: { $multiply: [ 0.5, "$price" ] } } }],
{ multi: true }
)
// { price: 9.995 }
// { price: 1.02 }
The first part {} is the match query, filtering which documents to update (all documents in this case).
The second part [{ $set: { price: ... } }] is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline). $set is a new aggregation operator and an alias of $addFields. Note how price is modified directly based on the its own value ($price).
Don't forget { multi: true }, otherwise only the first matching document will be updated.
Well, this is possible with an atomic operation as $set.
You have several options :
use the eval() solution proposed by pingw33n
retrieve the document you want to modify to get the current value and modify it with a set
if you have a high operation rate, you might want to be sure the focument has not changed during you fetch its value (using the previous solution) so you might want to use a findAndModify (see this page to get inspired on how to do it) operation.
It really depends on your context : with a very low pressure on the db, I'd go for the solution of pingw33n. With a very high operation rate, I'd use the third solution.