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
})
Related
I am new to mongodb. How to create a collection if not exists otherwise update.
try {
const { data } = Visit.findByIdAndUpdate(
'',
{ $inc: { counter: 1 } },
{ new: true }
);
} catch (err) {
return err;
}
This is my code to update a only one object Id. In that collection only one Object Id is present. When run this for first time, the collection is empty. So I want to check the collection exists. If exist update otherwise create a collection.
You want to use upsert, like so:
const { data } = await Visit.findByIdAndUpdate(
'',
{ $inc: { counter: 1 } },
{ new: true, upsert: true }
);
Upsert will:
If document(s) match the query criteria, db.collection.update() performs an update.
If no document matches the query criteria, db.collection.update() inserts a single document.
And the insert behavior if no collection exists is:
If the collection does not exist, then the insert() method will create the collection.
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.
I am struggling for a couple of hours to show the final value of an updated document (via mongoose updateOne). I successfully modify it as I can see "nModified: 1" when I call the endpoint on Postman, but I am not able to output the actual final document - even when using the parameter {new:true}
This is the code for the route:
// 3. We check if blockid is in this project
Block.findById(req.params.blockid)
.then(block => {
if (!block) {
errors.noblock = "Block not found";
return res.status(404).json(errors);
}
// 4. We found the block, so we modify it
Block.updateOne(
{ _id: req.params.blockid },
{ $set: blockFields }, // data to be updated
{ new: true }, // flag to show the new updated document
(err, block) => {
if (err) {
errors.noblock = "Block not found";
return res.status(404).json(errors);
}
console.log(block);
res.json(block);
}
);
})
.catch(err => console.error(err));
Instead, this is the output I am getting (Mongoose is on debug mode)
Any ideas?
Many thanks
{ new : true } will return the modified document rather than the original. updateOne doesn't have this option. If you need response as updated document use findOneAndUpdate.
Below are the mongoosejs function where you can use { new : true }
findByIdAndUpdate()
findOneAndUpdate()
findOneAndDelete()
findOneAndRemove()
findOneAndReplace()
Thank you #sivasankar for the answer. Here is the updated working version with findOneAndUpdate
And here the expected result:
you should give second param as object of keys value paris of data,
don't pass as $Set : blockfields, just add like below, if it is object containing parameters,
{ $set: blockFields }
Because code should be like this
Block.updateOne(
{ _id: req.params.blockid },
blockFields, // if blockfields is object containing parameters
{ new: true },
(err, block) => {
// lines of code
}
);
For more detail here is link to updateOne function detail updateOne
Following code gives me an exception in node js saying: "need to remove or update"
var args = {
query: { _id: _id },
update: { $set: data },
new: true,
remove: false
};
db.collection(COLLECTION.INVENTORY_LOCATION)
.findAndModify(args, function (err, results) {
if (err) {
return callback(err);
} else {
console.log(results);
callback(null, results);
}
});
Not able to figure out the issue as I have specified the update operation.
The syntax is different in the node driver than for the shell, which is the syntax you are using.
db.collection("collection_name").findAndModify(
{ _id: _id }, // query
[], // represents a sort order if multiple matches
{ $set: data }, // update statement
{ new: true }, // options - new to return the modified document
function(err,doc) {
}
);
There is a separate function for .findAndRemove()
As the documentation for the remove parameter of the findAndModify function states:
remove: <boolean>:
Must specify either the remove or the update field. Removes the
document specified in the query field. Set this to true to remove the
selected document . The default is false.
The default value is false so you don't have to provide it at all.
I believe the issue is that you are supplying both update and remove parameters. Try removing the remove parameter.
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
});