FindOne followed by updateOne, using a single query - mongodb

By using the "findOne" method of mongoDb driver (no mongoose), can I use the retrieved document to update it later?
For example:
document.collection('myCollection').findOne({ _id: myId }, (err, foundDoc) => {
// **I need to do some multiple checks in between**, so I don't want to use "findOneAndUpdate()"
// foundDoc is the retrieved document. Can I use it directly for update? (something like "foundDoc.update()")
document.collection('myCollection').updateOne({ _id: myId }, { $set: { name: 'John' } });
});
As you can see I am basically doing a second query by using the "updateOne" method (first it searches for the document and then it updates it). Can I avoid that somehow, and use the foundDoc directly for update?

If you want to update the same document, you don't have to call .findOne() followed by .updateOne() method. By default, upsert option is set to false in .upadateOne(), so it will refuse to insert the document if not found else it will update.
document.collection('myCollection').updateOne({ _id: myId }, { $set: { name: 'John' } });
.updateOne should be sufficient in your case.
Also if you want to add some filter conditions, .updateOne() supports that as below :
db.collection.updateOne(
<filter>, // you can place filters here
<update>,
{
upsert: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ]
}
)
Link : https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/

Related

Aggregation query with $set in findOneAndUpdate doesn't update document

I am using node 11.6, mongodb 4.2.5, mongoose 4.3.17. I am trying to update a field by adding a string to the end of it. I first updated to mongo 4.2 which I apparently needed to use aggregation pipelines in updates.
I tried following this post like this:
var update = [{$set: {slug: {$concat: ['$slug', '-rejected']}}}];
Content.findOneAndUpdate({_id: id}, update, {new: true}, (err, doc) => {
//
});
but when I ran it I got no error, no document returned, and it was not updated.
So I removed the outer [], and passed just an object like this:
var update = {$set: {slug: {$concat: ['$slug', '-rejected']}}}
Content.findOneAndUpdate({_id: id}, update, {new: true}, (err, doc) => {
//
});
And I receive this error message:
`Cast to string failed for value "{ '$concat': [ '$slug', '-rejected' ] }" at path "slug"`,
What does this mean? How can I accomplish this? (without two separate calls)
Running the same function but replacing update with:
var update = {slug: 'test-slug'}
successfully updates the slug to 'test-slug'.
But trying to do the same simple thing with an aggregation acts much like my previous attempt, no error or change:
var update = [{$set: {slug: 'test-sluggy'}}]
Using updateOne() instead of findOneAndUpdate() doesn't change anything either.
The only thing I can think that could cause it is the mongoose version, but it seems like there's a lot of changes between 4 and 5 and I don't want to update unless I have to, but I can't find anything that says it would change anything.
The pipeline form requires that the update be an array of pipeline stages.
Try wrapping your existing update in [] like
var update = [{$set: {slug: {$concat: ['$slug', '-rejected']}}}]
we can use something like that
var update = [{ $set: { slug: { $concat: ['$slug', '-rejected'] } } }]
starting from mongo versions > 4.2, the update operations can accept an aggregation pipeline, so we are able to update some field based on its current value:
the whole query may be something like that
Content.findOneAndUpdate(
{ _id: id }, // the find criteria
[{ $set: { slug: { $concat: ['$slug', '-rejected'] } } }], // this is the update aggregation pipeline, see the square brackets
{ multi: true }, // this should be set to true if you have more than one document to update,
// otherwise, only the first matching document will be updated,
// this is in case you use update rather than findOneAndUpdate,
// but here we have only one document to update, so we can ignore it
(err, doc) => {
//
});

Mongodb: concat to existing document

I have my collection like this:
{
"_id" : "ID1234",
"read_object" : "sss-ssss",
"expireAt" : ISODate("2020-04-30T22:00:00.000Z")
}
In case he encounters the same ID, I would like to update the read_object field, otherwise create a new document.
I tried to do it like this:
db.collection.update(
{ _id: "ID1234" },
{
$set: { read_object: { $concat: ["$read_object", "test"] } },
},
{ upsert: true }
)
but I get an error every time:
The dollar ($) prefixed field '$concat' in 'read_object.$concat' is not valid for storage.
If I add square brackets before $set, like this:
db.collection.update(
{ _id: "1b1b871493-14a0-4d21-bd74-086442df953c-2020-02" },
[{
$set: { read_object: { $concat: ["$read_object", "test"] } },
}],
{ upsert: true }
)
I get this error:
The dollar ($) prefixed field '$concat' in 'read_object.$concat' is not valid for storage.
Where do I have a mistake?
$concat is an aggregation operator, meaning you can't use it while using the basic update syntax as you can only use update operators on it.
With that said Mongo version 4.2 introduces pipeline updates, which is basically what you're trying to do with the square brackets.
Assuming you are using Mongo version 4.2 heres a working example:
db.test1.update({_id: "ID1234"}, [
{$set: {"read_object": {$concat: [{$ifNull: ["$read_object", ""]}, "test"]}}}
], {upsert: true});
Basically we just need to "replace" read_object if document does not exist as it is undefined in that case.
If you are using Mongo version that's smaller than 4.2 then unfortunately there is no way to do what you want in one operation, you'll have to first read the document and then adjust accordingly.

Updating the path 'x' would create a conflict at 'x'

This error happens when I tried to update upsert item:
Updating the path 'x' would create a conflict at 'x'
Field should appear either in $set, or in $setOnInsert. Not in both.
I had the same problem while performing an update query using PyMongo.
I was trying to do:
> db.people.update( {'name':'lmn'}, { $inc : { 'key1' : 2 }, $set: { 'key1' : 5 }})
Notice that here I'm trying to update the value of key1 from two MongoDB Update Operators.
This basically happens when you try to update the value of a same key with more than one MongoDB Update Operators within the same query.
You can find a list of Update Operators over here
If you pass the same key in $set and in $unset when updating an item, you will get that error.
For example:
const body = {
_id: '47b82d36f33ad21b90'
name: 'John',
lastName: 'Smith'
}
MyModel.findByIdAndUpdate(body._id, { $set: body, $unset: {name: 1}})
// Updating the path 'name' would create a conflict at 'name'
You cannot have the same path referenced more than once in an update. For example, even though the below would result in something logical, MongoDB will not allow it.
db.getCollection("user").updateOne(
{_id: ...},
{$set: {'address': {state: 'CA'}, 'address.city' : 'San Diego'}}
)
You would get the following error:
Updating the path 'address.city' would create a conflict at 'address'
db.products.update(
{ _id: 1 },
{
$set: { item: "apple" },
$setOnInsert: { defaultQty: 100 }
},
{ upsert: true }
)
Below is the key explanation to the issue:
MongoDB creates a new document with _id equal to 1 from the
condition, and then applies the $set AND $setOnInsert operations to
this document.
If you want a field value is set or updated regardless of insertion or update, use it in $set. If you want it to be set only on insertion, use it in $setOnInsert.
Here is the example: https://docs.mongodb.com/manual/reference/operator/update/setOnInsert/#example
Starting from MongoDB 4.2 you can use aggregate pipelines in update:
db.your_collection.update({
_id: 1
},
[{
$set:{
x_field: {
$cond: {
if: {$eq:[{$type:"$_id"} , "missing"]},
then: 'upsert value', // it's the upsert case
else: '$x_field' // it's the update case
}
}
}
}],
{
upsert: true
})
db.collection.bulkWrite() also supports it
With the Ruby library at least, it's possible to get this error if you have the same key twice, once as a symbol and once as a string:
db.getCollection("user").updateOne(
{_id: ...},
{$set: {'name': "Horse", name: "Horse"}}
)
I recently had the same issue while using the query below.
TextContainer.findOneAndUpdate({ blockId: req.params.blockId, 'content._id': req.params.noteId }, { $set: { 'content.note': req.body.note } }, { upsert: true, new: true })
When i have changed 'content.note' to 'content.$.note' it has been fixed. So my final query is :
TextContainer.findOneAndUpdate({ blockId: req.params.blockId, 'content._id': req.params.noteId }, { $set: { 'content.$.note': req.body.note } }, { upsert: true, new: true })

How to update a document in mongo db by matching user Id if it exists and insert if does not exist using upsert in meteor?

I want to update a document when it matches user Id entered in Url and if it does not exist then insert it.Here in my code update is working fine but insert has some issue as i am matching _id with given to update .so is there any other way to match user Id in document with given user Id ?
Anyone help me with this issue.
Below is my code:
var p = Dbs.findOne({ user_id: Router.current().params._id });
Dbs.update({ _id: p._id }, {
$set: {
user_id: user_id1,
cont: cont1,
add: add1,
pth: pth1
}
},
{upsert:true}
);
I tried using {User_id:p._id} in place of { _id: p._id } but its not working.
If I understand your question correctly you just want to match on user_id instead of _id. Then just use that in your selector:
Dbs.update({ user_id: your_user_id_value },
{
$set: {
user_id: user_id1,
cont: cont1,
add: add1,
pth: pth1
}
},
{ upsert: true}
);
This will work if there's only one document found. If the match finds multiple documents then only the first one will be modified unless you also specify multi: true in your options. Note also that this code will only work on the server.

MongoDB update document object

user = users.findOne({
"$or": [{
'local.email': 'some#email.com'
}, {
'google.email': 'some#email.com'
}, {
'facebook.email': 'some#email.com'
}]
// do stuff with user object
So I have the user object. This is fine, after I'm finished with what I need from it property wise I wish to update some of the fields in this object now, I've tried the following without it working:
user.local.email = 'other#email.com';
users.update(user);
Is this not a viable way of updating a document?
Use the $set operator to update your document as follows:
db.users.update(
{
"$or": [
{'local.email': 'some#email.com'},
{'google.email': 'some#email.com'},
{'facebook.email': 'some#email.com'}
]
},
{
$set: {
'local.email': 'other#email.com'
}
}
)
With the update method, you do not need to do another query which finds the document you want to update because the update() method takes in a query parameter which is the selection criteria for the update, the same query selectors as in the find() method are available. Read more on the update method in the Mongo docs here.
This was the better suited solution.
user.local.email = 'other#email.com';
users.update({
"$or": [{
'local.email': 'some#email.com'
}, {
'google.email': 'some#email.com'
}, {
'facebook.email': 'some#email.com'
}]
}, user);