Update MongoDb collection and not rewrite it - mongodb

I have a collection with the following objects:
{
...
"address": {
"addressLine1": "some address"
"city": "some city"
}
}
I need to update all objects to add address.fullName = address.addressLine1 (don't ask why :D) in cases when address exist.
I try to run this script in Robo 3T:
db.getCollection('suppliers').updateMany(
// query
{},
// update
{ $set: { address: { fullName: $address.address1 } } },
// options
{
// update only one document
"multi" : true,
// insert a new document, if no existing document match the query
"upsert" : false
}
);
I have an error
"$address isn't defined"
How should I write a script
To update address.fullName = address.addressLine1
Don't rewrite an entire object and subObject address (important)

Following this example from the MongoDB documentation, your update should be wrapped in an array like this.
db.getCollection('suppliers').updateMany(
{},
[
{
$set: {
'address.fullName': '$address.addressLine1',
},
},
],
{
multi: false,
upsert: false,
}
);
See a working example on MongoDB Playground

Related

Mongo update query for modifying array field failing

Important: Mongo Version I am using 3.4
Just Found out array filters is not available in 3.4
Any suggestion would be appreciated.
I have been trying this for days now I am trying to update particular comment text inside document. My document looks like this:
{
_id: "6ff1...<alphanumeric Id>",
name: "T-00001",
comment: [
{
_id: "6f7123....<alphanumeric Id>",
author: "6fsd...<alphanumeric Id pointing to user schema>"
text: "Hello",
edited: false
},
{
_id: "6f75323....<alphanumeric Id>",
author: "6ffgh21...<alphanumeric Id pointing to user schema>"
text: "Second Comment",
edited: false
}
]
}
What I am trying to achieve is to edit comment text here of particular comment id what my update query looks like is :
db.story.update(
{ _id: mongoose.Types.ObjectId(storyId) }, // Find that story story Id coming from body
{
$set : {
"comment.$[elem].text": "Update text with this",
"comment.$[elem].edited": true
}
},
{ "arrayFilters": [
{ "elem._id": mongoose.Types.ObjectId(commentId),
"elem.author": mongoose.Types.ObjectId(authorId)
} // Update only that comment.. Ids here coming from body
]}
)
Error Message I am getting
"errmsg" : "cannot use the part (comment of comment.$[elem].text) to traverse the element ({comment: [ { posted: new Date(1633092292670), delStatus: 0, _id: ObjectId('615702c4de4cf17dab996b4f'), by: \"User\", author: ObjectId('60ab52b1a3aa844774ca0fb3'), text: \"hello7\", edited: true }, { posted: new Date(1633699486732), delStatus: 0, _id: ObjectId('6160469e8aca585d3909708c'), by: \"User\", author: ObjectId('60ab52b1a3aa844774ca0fb3'), text: \"2021-10-08T13:24:46.671Z\" } ]})"
Found the solution god I am so bad....
db.story.update(
{
$and: [
{ _id: mongoose.Types.ObjectId(storyId)},
{ "comment._id": mongoose.Types.ObjectId(commentId)},
{ "comment.author": mongoose.Types.ObjectId(authorId)}
]
},
{
$set: {
"comment.$.text": body.text,
"comment.$.edited": true
}
}
)
With this I have a big problem whenever I hit this query it is updating some other comment until I remove the line:
{ "comment.author": mongoose.Types.ObjectId(authorId)}
Can someone please help?

mongodb find and update one with multiple conditions

{
roomId: "id",
questions:{
q1:{
user1:"user1's work"
}
}
}
I'm trying to query mongodb with multiple conditions, that roomId has to match, and questions must be q1, and in q1 there must be a user1.
Here's what I've tried so far. Using and operator, but doesn't seems to work.For now I'm using find, as I read in the docs that updateMany has the same query selector as find.
const result = await collection.find({
$and: [
{
roomId: roomId,
},
{
questions: {
currentQuestion: {
userName,
},
},
},
],
});
My schema:
{
roomId: "id",
roomName:"roomName",
questions:{
question1:{
user1:"user1's work",
userN: "userN's work"
},
questionN:{
user1:"",
userN:""
}
}
}
My expected input , (roomId, currentQuestion, userName) for query conditions,"userWork" to be inserted to what's under userName (user1 - userN).
Expected output, that the user's work gets updated with "userWork", under the right room, right currentQuestion and the right user.
You need this query I think:
db.collection.find({
"roomId": "id",
"questions.q1.user1": {
$exists: true
}
})
This query find a roomId which match your 'id' and then, check if exists the element questions.q1.user1.
Mongo playground example here
PS: You said update but... what do you want to update?
Assuming your schema is like
{
roomId: "id",
questions: {
q1: {
user1: "user1's work",
currentQuestion: "currentQuestion1"
}
}
}
Then, the query to update the currentQuestion field whose match the id and has existing questions.q1.user1 is this:
db.collection.update({
"roomId": "id",
"questions.q1.user1": {
$exists: true
}
},
{
"$set": {
"questions.q1.currentQuestion": "new question"
}
})
Example here
Note that if currentQuestion is one level up, you only have to modify $set object.
If you are not asking for this, please provide a complete schema, example input and expected output.

Is there a way to get only the found document as the result of findAndModify without the wrapping info

I have a findAndModify MongoDb request. The mongo server wraps the document in:
{
value: { ... },
lastErrorObject: { updatedExisting: true, n: 1 },
ok: 1
}
is there a way to only get the object from the value key?
{ ... }
The only way you get that kind of response is by directly running the findAndModify database command form, for example:
db.runCommand({
"findAndModify": "newdata",
"query": { "a": 1 },
"update": { "$set": { "b": 2 } },
"new": true,
"upsert": true
})
In the MongoDB shell and all driver implementations these methods are always wrapped with a collection method so that just the document which is modified ( or the original if you ask for that ) is returned in the response. In the shell the wrapping code looks like this:
function (args){
var cmd = { findandmodify: this.getName() };
for (var key in args){
cmd[key] = args[key];
}
var ret = this._db.runCommand( cmd );
if ( ! ret.ok ){
if (ret.errmsg == "No matching object found"){
return null;
}
throw "findAndModifyFailed failed: " + tojson( ret );
}
return ret.value;
}
So when you in fact just invoke that method from the collection object then you just get the document in response:
db.newdata.findAndModify({
"query": { "a": 1 },
"update": { "$set": { "b": 2 } },
"new": true,
"upsert": true
})
{ "_id" : ObjectId("5445af1ac745bf5663de72dd"), "a" : 1, "b" : 2 }
This is common to all drivers, some of which show alternate names to "findAndModify" such as "findAndUpdate" and specifically separate the "remove" and "update" functionality for each. Consult your language driver documentation for more details on other implementations, but they basically work in the same way.

MongoDB conditionally $addToSet sub-document in array by specific field

Is there a way to conditionally $addToSet based on a specific key field in a subdocument on an array?
Here's an example of what I mean - given the collection produced by the following sample bootstrap;
cls
db.so.remove();
db.so.insert({
"Name": "fruitBowl",
"pfms" : [
{
"n" : "apples"
}
]
});
n defines a unique document key. I only want one entry with the same n value in the array at any one time. So I want to be able to update the pfms array using n so that I end up with just this;
{
"Name": "fruitBowl",
"pfms" : [
{
"n" : "apples",
"mState": 1111234
}
]
}
Here's where I am at the moment;
db.so.update({
"Name": "fruitBowl",
},{
// not allowed to do this of course
// "$pull": {
// "pfms": { n: "apples" },
// },
"$addToSet": {
"pfms": {
"$each": [
{
"n": "apples",
"mState": 1111234
}
]
}
}
}
)
Unfortunately, this adds another array element;
db.so.find().toArray();
[
{
"Name" : "fruitBowl",
"_id" : ObjectId("53ecfef5baca2b1079b0f97c"),
"pfms" : [
{
"n" : "apples"
},
{
"n" : "apples",
"mState" : 1111234
}
]
}
]
I need to effectively upsert the apples document matching on n as the unique identifier and just set mState whether or not an entry already exists. It's a shame I can't do a $pull and $addToSet in the same document (I tried).
What I really need here is dictionary semantics, but that's not an option right now, nor is breaking out the document - can anyone come up with another way?
FWIW - the existing format is a result of language/driver serialization, I didn't choose it exactly.
further
I've gotten a little further in the case where I know the array element already exists I can do this;
db.so.update({
"Name": "fruitBowl",
"pfms.n": "apples",
},{
$set: {
"pfms.$.mState": 1111234,
},
}
)
But of course that only works;
for a single array element
as long as I know it exists
The first limitation isn't a disaster, but if I can't effectively upsert or combine $addToSet with the previous $set (which of course I can't) then it the only workarounds I can think of for now mean two DB round-trips.
The $addToSet operator of course requires that the "whole" document being "added to the set" is in fact unique, so you cannot change "part" of the document or otherwise consider it to be a "partial match".
You stumbled on to your best approach using $pull to remove any element with the "key" field that would result in "duplicates", but of course you cannot modify the same path in different update operators like that.
So the closest thing you will get is issuing separate operations but also doing that with the "Bulk Operations API" which is introduced with MongoDB 2.6. This allows both to be sent to the server at the same time for the closest thing to a "contiguous" operations list you will get:
var bulk = db.so.initializeOrderedBulkOp();
bulk.find({ "Name": "fruitBowl", "pfms.n": "apples": }).updateOne({
"$pull": { "pfms": { "n": "apples" } }
});
bulk.find({ "Name": "fruitBowl" }).updateOne({
"$push": { "pfms": { "n": "apples", "state": 1111234 } }
})
bulk.execute();
That pretty much is your best approach if it is not possible or practical to move the elements to another collection and rely on "upserts" and $set in order to have the same functionality but on a collection rather than array.
I have faced the exact same scenario. I was inserting and removing likes from a post.
What I did is, using mongoose findOneAndUpdate function (which is similar to update or findAndModify function in mongodb).
The key concept is
Insert when the field is not present
Delete when the field is present
The insert is
findOneAndUpdate({ _id: theId, 'likes.userId': { $ne: theUserId }},
{ $push: { likes: { userId: theUserId, createdAt: new Date() }}},
{ 'new': true }, function(err, post) { // do the needful });
The delete is
findOneAndUpdate({ _id: theId, 'likes.userId': theUserId},
{ $pull: { likes: { userId: theUserId }}},
{ 'new': true }, function(err, post) { // do the needful });
This makes the whole operation atomic and there are no duplicates with respect to the userId field.
I hope this helpes. If you have any query, feel free to ask.
As far as I know MongoDB now (from v 4.2) allows to use aggregation pipelines for updates.
More or less elegant way to make it work (according to the question) looks like the following:
db.runCommand({
update: "your-collection-name",
updates: [
{
q: {},
u: {
$set: {
"pfms.$[elem]": {
"n":"apples",
"mState": NumberInt(1111234)
}
}
},
arrayFilters: [
{
"elem.n": {
$eq: "apples"
}
}
],
multi: true
}
]
})
In my scenario, The data need to be init when not existed, and update the field If existed, and the data will not be deleted. If the datas have these states, you might want to try the following method.
// Mongoose, but mostly same as mongodb
// Update the tag to user, If there existed one.
const user = await UserModel.findOneAndUpdate(
{
user: userId,
'tags.name': tag_name,
},
{
$set: {
'tags.$.description': tag_description,
},
}
)
.lean()
.exec();
// Add a default tag to user
if (user == null) {
await UserModel.findOneAndUpdate(
{
user: userId,
},
{
$push: {
tags: new Tag({
name: tag_name,
description: tag_description,
}),
},
}
);
}
This is the most clean and fast method in the scenario.
As a business analyst , I had the same problem and hopefully I have a solution to this after hours of investigation.
// The customer document:
{
"id" : "1212",
"customerCodes" : [
{
"code" : "I"
},
{
"code" : "YK"
}
]
}
// The problem : I want to insert dateField "01.01.2016" to customer documents where customerCodes subdocument has a document with code "YK" but does not have dateField. The final document must be as follows :
{
"id" : "1212",
"customerCodes" : [
{
"code" : "I"
},
{
"code" : "YK" ,
"dateField" : "01.01.2016"
}
]
}
// The solution : the solution code is in three steps :
// PART 1 - Find the customers with customerCodes "YK" but without dateField
// PART 2 - Find the index of the subdocument with "YK" in customerCodes list.
// PART 3 - Insert the value into the document
// Here is the code
// PART 1
var myCursor = db.customers.find({ customerCodes:{$elemMatch:{code:"YK", dateField:{ $exists:false} }}});
// PART 2
myCursor.forEach(function(customer){
if(customer.customerCodes != null )
{
var size = customer.customerCodes.length;
if( size > 0 )
{
var iFoundTheIndexOfSubDocument= -1;
var index = 0;
customer.customerCodes.forEach( function(clazz)
{
if( clazz.code == "YK" && clazz.changeDate == null )
{
iFoundTheIndexOfSubDocument = index;
}
index++;
})
// PART 3
// What happens here is : If i found the indice of the
// "YK" subdocument, I create "updates" document which
// corresponds to the new data to be inserted`
//
if( iFoundTheIndexOfSubDocument != -1 )
{
var toSet = "customerCodes."+ iFoundTheIndexOfSubDocument +".dateField";
var updates = {};
updates[toSet] = "01.01.2016";
db.customers.update({ "id" : customer.id } , { $set: updates });
// This statement is actually interpreted like this :
// db.customers.update({ "id" : "1212" } ,{ $set: customerCodes.0.dateField : "01.01.2016" });
}
}
}
});
Have a nice day !

How can I rename a field for all documents in MongoDB?

Assuming I have a collection in MongoDB with 5000 records, each containing something similar to:
{
"occupation":"Doctor",
"name": {
"first":"Jimmy",
"additional":"Smith"
}
Is there an easy way to rename the field "additional" to "last" in all documents? I saw the $rename operator in the documentation but I'm not really clear on how to specify a subfield.
You can use:
db.foo.update({}, {
$rename: {
"name.additional": "name.last"
}
}, false, true);
Or to just update the docs which contain the property:
db.foo.update({
"name.additional": {
$exists: true
}
}, {
$rename: {
"name.additional": "name.last"
}
}, false, true);
The false, true in the method above are: { upsert:false, multi:true }. You need the multi:true to update all your records.
Or you can use the former way:
remap = function (x) {
if (x.additional) {
db.foo.update({
_id: x._id
}, {
$set: {
"name.last": x.name.additional
}, $unset: {
"name.additional": 1
}
});
}
}
db.foo.find().forEach(remap);
In MongoDB 3.2 you can also use
db.students.updateMany({}, {
$rename: {
"oldname": "newname"
}
})
The general syntax of this is
db.collection.updateMany(filter, update, options)
https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/
You can use the $rename field update operator:
db.collection.update(
{},
{ $rename: { 'name.additional': 'name.last' } },
{ multi: true }
)
If ever you need to do the same thing with mongoid:
Model.all.rename(:old_field, :new_field)
UPDATE
There is change in the syntax in monogoid 4.0.0:
Model.all.rename(old_field: :new_field)
Anyone could potentially use this command to rename a field from the collection (By not using any _id):
dbName.collectionName.update({}, {$rename:{"oldFieldName":"newFieldName"}}, false, true);
see FYI
I am using ,Mongo 3.4.0
The $rename operator updates the name of a field and has the following form:
{$rename: { <field1>: <newName1>, <field2>: <newName2>, ... } }
for e.g
db.getCollection('user').update( { _id: 1 }, { $rename: { 'fname': 'FirstName', 'lname': 'LastName' } } )
The new field name must differ from the existing field name. To specify a in an embedded document, use dot notation.
This operation renames the field nmae to name for all documents in the collection:
db.getCollection('user').updateMany( {}, { $rename: { "add": "Address" } } )
db.getCollection('user').update({}, {$rename:{"name.first":"name.FirstName"}}, false, true);
In the method above false, true are: { upsert:false, multi:true }.To update all your records, You need the multi:true.
Rename a Field in an Embedded Document
db.getCollection('user').update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } )
use link : https://docs.mongodb.com/manual/reference/operator/update/rename/
This nodejs code just do that , as #Felix Yan mentioned former way seems to work just fine , i had some issues with other snipets hope this helps.
This will rename column "oldColumnName" to be "newColumnName" of table "documents"
var MongoClient = require('mongodb').MongoClient
, assert = require('assert');
// Connection URL
//var url = 'mongodb://localhost:27017/myproject';
var url = 'mongodb://myuser:mypwd#myserver.cloud.com:portNumber/databasename';
// Use connect method to connect to the server
MongoClient.connect(url, function(err, db) {
assert.equal(null, err);
console.log("Connected successfully to server");
renameDBColumn(db, function() {
db.close();
});
});
//
// This function should be used for renaming a field for all documents
//
var renameDBColumn = function(db, callback) {
// Get the documents collection
console.log("renaming database column of table documents");
//use the former way:
remap = function (x) {
if (x.oldColumnName){
db.collection('documents').update({_id:x._id}, {$set:{"newColumnName":x.oldColumnName}, $unset:{"oldColumnName":1}});
}
}
db.collection('documents').find().forEach(remap);
console.log("db table documents remap successfully!");
}
If you are using MongoMapper, this works:
Access.collection.update( {}, { '$rename' => { 'location' => 'location_info' } }, :multi => true )