Update existing fields in mongodb - mongodb

For example:
db.getCollection('user')
.update(
{
"userDetails.username": "ky"
},
{
"$set":
{
"active": false,
"admin": false,
"phone": "08432808"
}
})
In the above code, if the document doesn't have the "active" field, mongodb will insert that field for you. I would like to get some error message in return in such situations, how can I achieve that?

You could achieve something like this using a foreach, filtering what you want to check and then verifying whether the property exists if not throw an exception. Can't say I'm a fan of this, I'd rather have the application logic verify the record exists and have a decision there of whether to update or do another action.
db.user.find({"userDetails.username": "ky"}).forEach(function(doc){
if (!doc.hasOwnProperty('active')){
throw "No active property";
}
else{
db.user.update({ _id: doc._id }, { "$set": { "active": false }})
}
})

Related

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.

Mongodb how to insert ONLY if does not exists (no update if exist)?

How can I insert a document if it does not exist while not updating existing document if it exists?
let's say I have a document as follows:
{
"company":"test",
"name":"nameVal"
}
I want to check whether the collection contains company test, if it doesn't exist I want to create a new document. If it exists I want to do NOTHING. I tried update with upsert = true. But it updates the existing document if it exists.
This is what I tried:
db.getCollection('companies').update(
{
"company": "test"
},
{
"company": "test",
"name": "nameVal2"
},
{upsert:true}
)
Appreciate any help to resolve this using one query.
You can use $setOnInsert like,
db.companies.updateOne(
{"company": "test"},
{ $setOnInsert: { "name": "nameVal2", ... } },
{ upsert: true }
)
If this update operation does not do insert, $setOnInsert won't have any effect. So, the name will be updated only on insert.
Edited: I was informed that my solution only works for an array which is not what the original post asked for.
If you are using an array you can use the $addToSet: operator instead of $SetOnInsert.
db.companies.updateOne(
{"company": "test"},
{ $addToSet: { ["name": "nameValue"]} },
{ new: true })
)

How is findById() + save() different from update() in MongoDB

While trying to update a MongoDB document using Mongoose, can I use a findById() with a save() in the callback, or should I stick with traditional update methods such as findByIdAndModify, findOneAndModify, update(), etc.? Say I want to update the name field of the following document (please see a more elaborate example in the edit at the end, which motivated my question):
{
"_id": ObjectId("123"),
"name": "Development"
}
(Mongoose model name for the collection is Category)
I could do this:
Category.update({ "_id" : "123" }, { "name" : "Software Development" }, { new: true })
or I could do this:
Category.findById("123", function(err, category) {
if (err) throw err;
category.name = "Software Development";
category.save();
});
For more elaborate examples, it feels easier to manipulate a JavaScript object that can simply be saved, as opposed to devising a relatively complex update document for the .update() operation. Am I missing something fundamentally important?
Edited 7/21/2016 Responding to the comment from #Cameron, I think a better example is warranted:
{
"_id": ObjectId("123"),
"roles": [{
"roleId": ObjectId("1234"),
"name": "Leader"
}, {
"roleId": ObjectId("1235"),
"name": "Moderator"
}, {
"roleId": ObjectId("1236"),
"name": "Arbitrator"
}]
}
What I am trying to do is remove some roles as well as add some roles in the roles array of sub-documents in a single operation. To add role sub-documents, $push can be used and to remove role sub-documents, $pull is used. But if I did something like this:
Person.update({
"_id": "123"
}, {
$pull : {
"roles" : {
"roleId" : {
$in : [ "1235", "1236" ]
}
}
},
$push : {
"roles" : {
$each: [{
"roleId" : ObjectId("1237"),
"name" : "Developer"
}]
}
}
}
When I try to execute this, I get the error Cannot update 'roles' and 'roles' at the same time, of course. That's when I felt it is easier to find a document, manipulate it any way I want and then save it. In that scenario, I don't know if there is really any other choice for updating the document.
I typically like to use findById() when I am performing more elaborate updates and don't think you are missing anything fundamentally important.
However one method to be aware of in mongoose is findByIdAndUpdate(), this issues a mongodb findAndModify update command and would allow you to perform your first example with the following code: Category.findByIdAndUpdate("123", function(err, savedDoc) {...}).

Error "Uncaught Error: When the modifier option is true, validation object must have at least one operator" when trying to update

I'm getting error message Uncaught Error: When the modifier option is true, validation object must have at least one operator
I've looked at a number of similar questions, but haven't been able to decipher what's wrong with my query:
Meteor.users.update({
_id: Meteor.user()._id
}, {
$set: {
"emails.letter": true
}
})
My MongoDB structure is as follows:
{
"_id": "ujkwQp4rYTKQeLq3F",
"emails": [{
"address": "admin#admin.com",
"letter": false
}]
}
You need to specify which element in the array you want to update. Try this:
Meteor.users.update({
_id : 123456,
"emails.address":"admin#admin.com"
}, {
$set: {
"emails.$.letter": true
}
});

Upsert Document and/or add a Sub-Document

I've been wrestling with the asynchronous nature of MongoDB, Mongoose and JavaScript and how to best make multiple updates to a collection.
I have an Excel sheet of client and contact data. There are some clients with multiple contacts, one per line, and the client data is the same (so the client name can be used as a unique key - in fact in the schema it's defined with unique: true).
The logic I want to achieve is:
Search the Client collection for the client with clientName as the key
If a matching clientName isn't found then create a new document for that client (not an upsert, I don't want to change anything if the client document is already in the database)
Check to see if the contact is already present in the array of contacts within the client document using firstName and lastName as the keys
If the contact isn't found then $push that contact onto the array
Of course, we could easily have a situation where the client doesn't exists (and so is created) and then immediately, the very next row of the sheet, is another contact for the same client so then I'd want to find that existing (just created) client and $push that 2nd new contact into the array.
I've tried this but it's not working:
Client.findOneAndUpdate(
{clientName: obj.client.clientname},
{$set: obj.client, $push: {contacts: obj.contact}},
{upsert: true, new: true},
function(err, client){
console.log(client)
}
)
and I've had a good look at other questions, e.g.:
create mongodb document with subdocuments atomically?
https://stackoverflow.com/questions/28026197/upserting-complex-collections-through-mongoose-via-express
but can't get a solution... I'm coming to the conclusion that maybe I have to use some app logic to do the find, then decisions in my code, then writes, rather than use a single Mongoose/Mongo statement, but then the issues of asynchronicity rear their ugly head.
Any suggestions?
The approach to handling this is not a simple one, as mixing "upserts" with adding items to "arrays" can easily lead to undesired results. It also depends on if you want logic to set other fields such as a "counter" indicating how many contacts there are within an array, which you only want to increment/decrement as items are added or removed respectively.
In the most simple case however, if the "contacts" only contained a singular value such as an ObjectId linking to another collection, then the $addToSet modifier works well, as long as there no "counters" involved:
Client.findOneAndUpdate(
{ "clientName": clientName },
{ "$addToSet": { "contacts": contact } },
{ "upsert": true, "new": true },
function(err,client) {
// handle here
}
);
And that is all fine as you are only testing to see if a doucment matches on the "clientName", if not upsert it. Whether there is a match or not, the $addToSet operator will take care of unique "singular" values, being any "object" that is truly unique.
The difficulties come in where you have something like:
{ "firstName": "John", "lastName": "Smith", "age": 37 }
Already in the contacts array, and then you want to do something like this:
{ "firstName": "John", "lastName": "Smith", "age": 38 }
Where your actual intention is that this is the "same" John Smith, and it's just that the "age" is not different. Ideally you want to just "update" that array entry end neiter create a new array or a new document.
Working this with .findOneAndUpdate() where you want the updated document to return can be difficult. So if you don't really want the modified document in response, then the Bulk Operations API of MongoDB and the core driver are of most help here.
Considering the statements:
var bulk = Client.collection.initializeOrderedBulkOP();
// First try the upsert and set the array
bulk.find({ "clientName": clientName }).upsert().updateOne({
"$setOnInsert": {
// other valid client info in here
"contacts": [contact]
}
});
// Try to set the array where it exists
bulk.find({
"clientName": clientName,
"contacts": {
"$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}
}
}).updateOne({
"$set": { "contacts.$": contact }
});
// Try to "push" the array where it does not exist
bulk.find({
"clientName": clientName,
"contacts": {
"$not": { "$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}}
}
}).updateOne({
"$push": { "contacts": contact }
});
bulk.execute(function(err,response) {
// handle in here
});
This is nice since the Bulk Operations here mean that all statements here are sent to the server at once and there is only one response. Also note here that the logic means here that at most only two operations will actually modify anything.
In the first instance, the $setOnInsert modifier makes sure that nothing is changed when the document is just a match. As the only modifications here are within that block, this only affects a document where an "upsert" occurs.
Also note on the next two statements you do not try to "upsert" again. This considers that the first statement was possibly successful where it had to be, or otherwise did not matter.
The other reason for no "upsert" there is because the condtions needed to test the presence of the element in the array would lead to the "upsert" of a new document when they were not met. That is not desired, therefore no "upsert".
What they do in fact is respectively check whether the array element is present or not, and either update the existing element or create a new one. Therefore in total, all operations mean you either modify "once" or at most "twice" in the case where an upsert occurred. The possible "twice" creates very little overhead and no real problem.
Also in the third statement the $not operator reverses the logic of the $elemMatch to determine that no array element with the query condition exists.
Translating this with .findOneAndUpdate() becomes a bit more of an issue. Not only is it the "success" that matters now, it also determines how the eventual content is returned.
So the best idea here is to run the events in "series", and then work a little magic with the result in order to return the end "updated" form.
The help we will use here is both with async.waterfall and the lodash library:
var _ = require('lodash'); // letting you know where _ is coming from
async.waterfall(
[
function(callback) {
Client.findOneAndUpdate(
{ "clientName": clientName },
{
"$setOnInsert": {
// other valid client info in here
"contacts": [contact]
}
},
{ "upsert": true, "new": true },
callback
);
},
function(client,callback) {
Client.findOneAndUpdate(
{
"clientName": clientName,
"contacts": {
"$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}
}
},
{ "$set": { "contacts.$": contact } },
{ "new": true },
function(err,newClient) {
client = client || {};
newClient = newClient || {};
client = _.merge(client,newClient);
callback(err,client);
}
);
},
function(client,callback) {
Client.findOneAndUpdate(
{
"clientName": clientName,
"contacts": {
"$not": { "$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}}
}
},
{ "$push": { "contacts": contact } },
{ "new": true },
function(err,newClient) {
newClient = newClient || {};
client = _.merge(client,newClient);
callback(err,client);
}
);
}
],
function(err,client) {
if (err) throw err;
console.log(client);
}
);
That follows the same logic as before in that only two or one of those statements is actually going to do anything with the possibility that the "new" document returned is going to be null. The "waterfall" here passes a result from each stage onto the next, including the end where also any error will immediately branch to.
In this case the null would be swapped for an empty object {} and the _.merge() method will combine the two objects into one, at each later stage. This gives you the final result which is the modified object, no matter which preceeding operations actually did anything.
Of course, there would be a differnt manipulation required for $pull, and also your question has input data as an object form in itself. But those are actually answers in themselves.
This should at least get you started on how to approach your update pattern.
I don't use mongoose so I'll post a mongo shell update; sorry for that. I think the following would do:
db.clients.update({$and:[{'clientName':'apple'},{'contacts.firstName': {$ne: 'nick'}},{'contacts.lastName': {$ne: 'white'}}]},
{$set:{'clientName':'apple'}, $push: {contacts: {'firstName': 'nick', 'lastName':'white'}}},
{upsert: true });
So:
if the client "apple" does not exists, it is created, with a contact with given first and last name. If it exists, and does not have the given contact, it has it pushed. If it exists, and already has the given contact, nothing happens.