Set value of a key with value of another key in mongodb directly with query - mongodb

Can I create a query, something like this below
db.getCollection('walkins.businesses').update(
{$and:[{"loyaltyModule.isPublished": true},{"loyaltyModule.publishAt": {"$eq":null}}]},
{$set : {"loyaltyModule.publishAt":"this.loyaltyModule.creationAt"}}, {multi:true}
)
to set value of creationAt as publishAt using update query directly where creationAt is already in collection.
Can I set the value of publishAt using another field creationAt in the same document?

With Aggregate
The best way to do this is to use the aggregation framework to compute our new field.
using the $addFields and the $out aggregation pipeline operators.
db.getCollection('walkins.businesses').aggregate(
[
{$match : {$and:[{"loyaltyModule.isPublished": true},{"loyaltyModule.publishAt": {"$eq":null}}]}},
{ "$addFields": {
"loyaltyModule.publishAt":"loyaltyModule.creationAt"
}},
{ "$out": "collection" }
]
)
Note that this does not update your collection but instead replace the existing collection or create a new one. Also for update operations that require "type casting" you will need client side processing, and depending on the operation, you may need to use the find() method instead of the .aggreate() method
With Iteration of cursor
you can iterate the cursor and update
db.getCollection('walkins.businesses').find({$and:[{"loyaltyModule.isPublished": true},{"loyaltyModule.publishAt": {"$eq":null}}]}).forEach(function(x){
db.getCollection('walkins.businesses').update({_id : x._id },
{$set : {"loyaltyModule.publishAt": x.loyaltyModule.creationAt}},
{multi:true}
)
})
here, you can't update multiple records at one update query due to update happening by matching with _id field

Related

Mongoose Updating an array in multiple documents by passing an array of filters to update query

I have multiple documents(3 documents in this example) in one collection that looks like this:
{
_id:123,
bizs:[{_id:'',name:'a'},{_id:'',name:'b'}]
},
{
_id:456,
bizs:[{_id:'',name:'e'},{_id:'',name:'f'}]
}
{
_id:789,
bizs:[{_id:'',name:'x'},{_id:'',name:'y'}]
}
Now, I want to update the bizs subdocument by matching with my array of ids.
That is to say, my array filter for update query is [123,789], which will match against the _id fields of each document.
I have tried using findByIdAndUpdate() but that doesn't allow an array for the update query
How can I update the 2 matching documents (like my example above) without having to put findByIdAndUpdate inside a forloop to match the array element with the _id?
You can not use findByIdAndUpdate when updating multiple documents, findByIdAndUpdate is from mongoose which is a wrapper to native MongoDB's findOneAndUpdate. When you pass a single string as a filter to findByIdAndUpdate like : Collection.findByIdAndUpdate({'5e179dac627ef7823643cd97'}, {}) - then mongoose will internally convert string to ObjectId() & form it as a filter like :_id : ObjectId('5e179dac627ef7823643cd97') to execute findOneAndUpdate. So it means you can only update one document at a time, So if you've multiple documents to be updated use update with option {multi : true} or updateMany.
Assume if you wanted to push a new object to bizs, this is how query looks like :
collection.updateMany({ _id: { $in: [123, 456] } }, {
$push: {
bizs: {
"_id": "",
"name": "new"
}
}
})
Note : Update operations doesn't return the documents in response rather they will return write result which has information about n docs matched & n docs modified.

Inner query using mongo template

I am new to MongoDB and Spring mongotemplate. I would like to build a query using mongotemplate whose equivalent in Postgres would be
select * from feedback
where feedback.outletId in (
select outletId from feedback
where feedback.createdOn >= '2013-05-03'::date
)
Is this even possible in MongoDB?
Well there is no concept of inner queries in MongoDB so basically it can be achieved by 2 queries but probably you already know that and want a 'better' solution. Since you asked if it is possible, I think it can be achieved by aggregation however that can be tricky.
db.feedback.aggregate([
{$project : {
'outletId' : 1,
'feedback._id' : '$_id',
'feedback.createdOn' : '$createdOn',
'feedback.a' : '$a'
}},
{$group : {
_id : $outletId,
feedbacks : {$addToSet : '$feedback'}
}},
{$match : {
'feedbacks.createdOn' : {
$gte : ISODate('2013-05-03')}
}},
{$unwind : '$feedback'}]);
You can add one more $project stage in the end to turn child object into values as it was in the document. I know it doesn't look pretty, but I would explain it stage by stage,
first we project a document with putting all the needed field inside a child field called feedback,
in second stage we grouped it by outletId and put all the child feedback into an array named feedbacks,(so for each outletid we get all feedbacks).
in third stage we use $match to filter out where there is not even a single feedback in array which createdOn field is greater than set date,
after those outletIds are filtered out we call unwind to get each child in feedbacks array as a single document.
Now if we talk about mongoTemplate, yes it accepts all these parameter for aggregation including the nesting of child in feedback in first stage. just see some example of TypedAggregation
if you are saving the createdOn field as a string instead of timestamp or ISODate even normal mongo queries won't work on that when you need to find range as its working in your postgres example.
Hope it helps.

Use $not or $ne in update query

Should I use $not or $ne in the query:
Mytable.update({ TheThing: Thing,
'UpdatedInfo.NewInfo': {$ne: ThisNewestInfo} }, {
$push: {
UpdatedInfo: {
TheDate: ThisDate,
NewInfo: ThisNewestInfo,
Past: OriginalInfo
}
}
},
function (err, result) {
if (err){
throw new Error(err.message);
}
}
If I only want to update the document when ThisNewestInfo is not already present in UpdatedInfo array, in NewInfo object element. Trying to understand the difference between $not and $ne.
And also:
If the document does not contain UpdatedInfofield in the beginning? How should I change the update query above? Meaning that if UpdatedInfodoes not exists it adds UpdatedInfo, and later on, say next day, checks if ThisNewestInfois not already present when updating document again.
It depends on your collection actually.
The main different between $ne and $not in this scenario is that, $not performs a logical disjunction. That is if your document didn't had an UpdatedInfo field, using $not would have pushed the document while using $ne nothing would have happened to that document.
So if all your document of collection has UpdatedInfo field, its better to go with $ne.
Edit
Based on your edit you mentioned UpdatedInfo might not be present in document. In such cases you should use $not. $ne wont be able to update docs that doesn't have UpdatedInfo field.
Remember like this: $not checks for presence of key as well as value, while $ne checks only for value and ignores document that doesn't have the particular key in query.

In MongoDB, method to keep the previous value of a field in a different field while updating an object?

Say I have an object with field state, I want to update this field, while keeping the previous value of state in previous_state field. First, I have tried to make an update with unset-rename-set:
collection.update(query, {$unset: {previous_state: ""}, $rename: {state: "previous_state"}, $set: {state: value}})
no surprise it did not work. After reading:
Update MongoDB field using value of another field
MongoDB update: Generate new field based on existing field, or update in place
Update field with another field's value in the document
I am nearly convinced that I do not have a solution to perform this in a single query. So the question is what is the best practice to do it?
There are various ways to do it, depending on the version of MongoDB, and they are described in this answer from another thread: https://stackoverflow.com/a/37280419/5538923 .
For MongoDB 3.4+, for example, there is this query that can be put in MongoSH:
db.collection.aggregate(
[
{ "$addFields": {
"previous_state": { "$concat": [ "$state" ] },
"state": { "$concat": [ "$state", " customly modified" ] }
}},
{ "$out": "collection" }
])
Also note that this query works only when the MongoDB instance is not sharded. When sharded (e.g., often the case in Microsoft Azure CosmosDB), the method described in that answer for MongoDB 3.2+ works, or alternatively put a new collection as destination (in the $out close), and then import the data in the original collection, after removing all the data there.
One solution (if you've got onlty one writer) could be to trigger your update in two steps:
> var previousvalue = collection.findOne(query).state;
> collection.update(query, {$set: {"previous_state": previousvalue, "state": newstatevalue}});

Add new field to every document in a MongoDB collection

How can I add a new field to every document in an existent collection?
I know how to update an existing document's field but not how to add a new field to every document in a collection. How can I do this in the mongo shell?
Same as the updating existing collection field, $set will add a new fields if the specified field does not exist.
Check out this example:
> db.foo.find()
> db.foo.insert({"test":"a"})
> db.foo.find()
{ "_id" : ObjectId("4e93037bbf6f1dd3a0a9541a"), "test" : "a" }
> item = db.foo.findOne()
{ "_id" : ObjectId("4e93037bbf6f1dd3a0a9541a"), "test" : "a" }
> db.foo.update({"_id" :ObjectId("4e93037bbf6f1dd3a0a9541a") },{$set : {"new_field":1}})
> db.foo.find()
{ "_id" : ObjectId("4e93037bbf6f1dd3a0a9541a"), "new_field" : 1, "test" : "a" }
EDIT:
In case you want to add a new_field to all your collection, you have to use empty selector, and set multi flag to true (last param) to update all the documents
db.your_collection.update(
{},
{ $set: {"new_field": 1} },
false,
true
)
EDIT:
In the above example last 2 fields false, true specifies the upsert and multi flags.
Upsert: If set to true, creates a new document when no document matches the query criteria.
Multi: If set to true, updates multiple documents that meet the query criteria. If set to false, updates one document.
This is for Mongo versions prior to 2.2. For latest versions the query is changed a bit
db.your_collection.update({},
{$set : {"new_field":1}},
{upsert:false,
multi:true})
Since MongoDB version 3.2 you can use updateMany():
> db.yourCollection.updateMany({}, {$set:{"someField": "someValue"}})
To clarify, the syntax is as follows for MongoDB version 4.0.x:
db.collection.update({},{$set: {"new_field*":1}},false,true)
Here is a working example adding a published field to the articles collection and setting the field's value to true:
db.articles.update({},{$set: {"published":true}},false,true)
db.collection.updateMany({}, {$set: {"fieldName": ""}})
updateMany requires a matching condition for each document, since we are passing {} it is always true. And the second argument uses $set operator to add the required field in each document.
Pymongo 3.9+
update() is now deprecated and you should use replace_one(), update_one(), or update_many() instead.
In my case I used update_many() and it solved my issue:
db.your_collection.update_many({}, {"$set": {"new_field": "value"}}, upsert=False, array_filters=None)
if you are using mongoose try this,after mongoose connection
async ()=> await Mongoose.model("collectionName").updateMany({}, {$set: {newField: value}})
The answers above does't cover this scenario. I was looking for the similar query but want to add fields to few documents based on condition.
So, we can use first variable of updateMany to update fields only in few documents.
Example: I want to add a nullable field isDeprecated? to all those Users whose userType is Employer and has country "AAA".
db.users.updateMany({"userType": "Employer", "country": "AAA"}, {"$set": { "isDeprecated?": true }})
This answer will be helpful in those scenarios as well, where we have to find the collection and then update. This can we done in single query like mentioned.