How to properly do a Bulk upsert/update in MongoDB - node.js

I'm trying to:
Find a document according to a search criteria,
If found, update some attributes
If not insert a document with some attributes.
I'm using a Bulk.unOrderedOperation as I'm also performing a single insert. And I want to do everything in one operation againast DB.
However something it's causing nothing is being inserted for the update/upsert operation.
This is the insert document:
var lineUpPointsRoundRecord = {
lineupId: lineup.id, // String
totalPoints: roundPoints, // Number
teamId: lineup.team, // String
teamName: home.team.name, // String
userId: home.iduser, // String
userName: home.user.name, // String
round: lineup.matchDate.round, // Number
date: new Date()
}
This is the upsert document:
var lineUpPointsGeneralRecord = {
teamId: lineup.team, // String
teamName: home.team.name, // String
userId: home.iduser, // String
userName: home.user.name, // String
round: 0,
signupPoints: home.signupPoints, // String
lfPoints: roundPoints+home.signupPoints, // Number
roundPoints: [roundPoints] // Number
};
This is how I'm trying to upsert/update:
var batch = collection.initializeUnorderedBulkOp();
batch.insert(lineUpPointsRoundRecord);
batch.find({team: lineUpPointsRoundRecord.teamId, round: 0}).
upsert().
update({
$setOnInsert: lineUpPointsGeneralRecord,
$inc: {lfPoints: roundPoints},
$push: {roundPoints: roundPoints}
});
batch.execute(function (err, result) {
return cb(err,result);
});
Why wouldn't it be upserting/updating?
Note
That is JS code using waterline ORM which also uses mongodb native driver.

Your syntax here is basically correct, but your general execution was wrong and you should have "seperated" the "upsert" action from the other modifications. These will otherwise "clash" and produce an error when an "upsert" occurs:
LineupPointsRecord.native(function (err,collection) {
var bulk = collection.initializeOrderedBulkOp();
// Match and update only. Do not attempt upsert
bulk.find({
"teamId": lineUpPointsGeneralRecord.teamId,
"round": 0
}).updateOne({
"$inc": { "lfPoints": roundPoints },
"$push": { "roundPoints": roundPoints }
});
// Attempt upsert with $setOnInsert only
bulk.find({
"teamId": lineUpPointsGeneralRecord.teamId,
"round": 0
}).upsert().updateOne({
"$setOnInsert": lineUpPointsGeneralRecord
});
bulk.execute(function (err,updateResult) {
sails.log.debug(err,updateResult);
});
});
Make sure your sails-mongo is a latest version supporting the Bulk operations properly be the inclusion of a recent node native driver. The most recent supports the v2 driver, which is fine for this.

I recommend use bulkWrite exemplary code with bulk upsert of many documents:
In this case you will create documents with unique md5. If document exists then will be updated but no new document is created like in classical insertMany.
const collection = context.services.get("mongodb-atlas").db("master").collection("fb_posts");
return collection.bulkWrite(
posts.map(p => {
return { updateOne:
{
filter: { md5: p.md5 },
update: {$set: p},
upsert : true
}
}
}
),
{ ordered : false }
);
https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/

Typically I have always set upsert as a property on update. Also update should be able to find the record itself so no need to find it individually.
Depending on the environment the $ may or may not be necessary.
batch.update(
{team: lineUpPointsRoundRecord.teamId, round: 0},
{
$setOnInsert: lineUpPointsGeneralRecord,
$inc: {lfPoints: roundPoints},
$push: {roundPoints: roundPoints},
$upsert: true
});

Related

Mongoose: updateMany() is not working as expected

I'm using mongoose to handle my DB queries. I'm trying to update a set of records entirely using this method. Mode code looks like this:
// prepare database query
const filter = { type: 'company' };
const update = req.body.payload; // payload contains the array of objects (i.e. updated records)
const options = { new: true, runValidators: true }
// find and update the taxonomy record
await Taxonomy.updateMany(filter, update, options);
But whenever I run this query I'm getting following error in the console:
Error [MongooseError]: Invalid update pipeline operator: "_id"
I suppose there is something wrong in my update payload. The req.body.payload looks like this:
[
{
_id: '5ef3d08c745428001d92f896',
type: 'company',
name: 'Company Size',
__v: 0
},
{
_id: '5ef3cdc5745428001d92f893',
type: 'company',
name: 'Company Industry',
__v: 0
}
]
Can you please tell me what actually is wrong here?
This is not the right usage of updateMany() - it is aimed to update many documents with a single change.
To update many documents use bulkwrite() (docs) :
async function myUpdateMany(Model, objectsArray) {
try {
let ops = []
for (let obj of (objectsArray || [])) {
ops.push({
updateOne: {
filter: { platformId: obj.platformId },
update: obj,
upsert: false, // set "true" if you want to add a new document if it doesn't exist
}
})
}
Model.bulkWrite(ops, { ordered: false });
} catch (err) {
throw Error("myUpdateMany error: " + err)
}
}
Regarding runValidators, according to this, it seems to work by default.

MongoDB find an object in DB and update its field

Problem
I have a NodeJS app connecting to a MongoDB. I am tracking how many times something occurred. So, what I want is:
Check if my constructed object is in the database (excluding field with number of occurrences)
If so, update its occurrences +=1
If not, set occurrences = 1 and insert it
I have a working code:
const isInDb = await collection.findOne({
// match all other fields except for the occurrences field
});
if(!isInDb) {
parsedElement.occurrences = 1;
await collection.insertOne(parsedElement);
} else {
await collection.updateOne(isInDb, { $inc: { "occurrences": 1 } });
}
My question
Isn't there a better way? Ideally, it'd be something like collection.findAndUpdate or with upsert or something similar. What I wrote is functional, but seems inefficient to me, since I first have to query the DB for a look-up, and then query it for update.
updateOne takes a third parameter for options. Set upsert: true.
collection.updateOne({ /* match properties */ }, { $inc: { "occurrences": 1 } }, { upsert: true })
collection.updateOne({ /* match properties */ }, {
$set: parsedElement,
$inc: {
"occurrences": 1
}
}, {
upsert: true
})

Mongoose get collection data if present the update otherwise insert

I am new to mongodb and mongoose with ExpressJS. It may be simple question but I dont success.
var BookCounter = new Schema({
counter: {type: Number,},
book: {type: String, min: 18}
});
this is the my schema and the following is the my question:
I want to check that xyz book is present or not
if book is present
Then I have to update the book counter by one.
otherwise
I have to insert new book.
Can you please help me ..
Thanks in advance :)
You need an update operation findOneAndUpdate() that uses the options upsert to create the document if it does not exist and the new option if set to true returns the newly created/modified document rather than the original, alongside the $set and $inc field update operators.
The following example demonstrates this:
var query = { "book": "xyz" },
update = { "$inc": { "counter": 1 } },
options = { "upsert": true, "new": true };
// Find the document
Book.findOneAndUpdate(query, update, options, function(err, result) {
if (err) handleError(err);
else {
// do something with the document
console.log(JSON.stringify(result, null, 4));
}
});

Mongoose: how to check if document is modified via model.findOneAndUpdate()

In mongoose, we can check if an update operation has modified the document with model.update():
model.update(query, update, function(err, raw){
if (raw.nModified >= 1) console.log('document is modified!')
});
Is there a way to do the same with model.findOneAndUpdate()?
model.findOneAndUpdate(query, update, { new: true }, function(err, doc){
if (doc) {
// So MongoDB found the document, but is there a way
// to know the document was indeed modified?
}
});
You can pass the option { passRawResult : true } to mongoose to advice mongoose to pass the raw result of the underlying mongodb driver, in this case mongodb-native, as a third argument to the callback.
mongodb-native documentation for findOneAndUpdate
model.findOneAndUpdate(query, update, { new: true, passRawResult : true }, function(err, doc, res){
// res will look like
// { value: { _id: 56a9fc80a7f9a4d41c344852, name: 'hugo updated', __v: 0 },
// lastErrorObject: { updatedExisting: true, n: 1 },
// ok: 1 }
});
In case the update did not succeed due to no matching document was found a null res will be passed to the callback. In case a document matched but field values where the same as before the update res object will not give you enough information to figure out if values were updated for the matching document.

Mongoose: Get doc _id after upsert

is there any way to get the record _id after an upsert?
I've seen this post (How to insert a doc into mongodb using mongoose and get the generated id?), but this is oriented only to inserts, not updates.
Also, using the MongoDB you can use get the _id using getlasterror (https://groups.google.com/forum/?fromgroups=#!topic/mongoose-orm/ehZ11QY-OUw), but Mongoose doesn't provides access to it (https://groups.google.com/forum/?fromgroups=#!topic/mongoose-orm/pSv6WrasvWg)
Thanks
Use Mongoose's findOneAndUpdate method with upsert: true in the options object.
var query = { name: 'borne' },
data = { name: 'jason borne' },
options = { upsert: true };
Model.findOneAndUpdate(query, data, options, function (err, object) {
/* use object._id */
});
Another possibility with promises:
Model.findOneAndUpdate(query, data, options, function (err, object) {
})).then((result) => {
/* return result.upserted[0]._id */
})
...where result is the following:
{ n: 1,
nModified: 0,
upserted: [ { index: 0, _id: /* some id */ } ],
ok: 1 }
If you want the updated document returned, and in cases where it didn't exist and was upserted the new document. Below is the option you need to set.
set new: true to the options: options = { upsert: true, new: true };
Source: Based on Jamiel's comment, I am adding his comment as an answer as it was hard time finding for me to get that _id when no document existed and created by upsert (And I was trying to create my own extended method).

Resources