Mongoose get collection data if present the update otherwise insert - node.js

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));
}
});

Related

mongodb query events done to an item and group them into an array

I know that the title might be a bit vague, but I will elaborate here.
basically what I am trying to achieve is the following:
I have a collection with documents that have the following scheme:
bookId: <uuid>
genre: <string>
isTaken: true
historyIndex: each time something happens i increment this
returnedAt: not required but will be present on documents with historyIndex
takenAt: not required but will be present on documents with historyIndex
there are documents with no historyIndex field because they are representing the book itself without the action that were done to the book.
what i want to do is this:
I want to query the books by their unique uuid and then use the documents with historyIndex and add them to the main documents as in an array as called bookEvents
so the final results will be
bookId:
bookEvents: [] --> an array with all the entries that contain history index
basically everytime the status of the book changes, i am inserting an event with the date it was taken on and the date it was returned on
What would be the best way of achieving such thing ?
Should I query once and iterate in my code ?
Should I query twice (once where the fields exist and once where they don't) ?
Thank you in advance for all the people that will try to help!
You can use the plugin or events to achieve this.
var CounterSchema = new mongoose.Schema({
_id: {type: String, required: true},
seq: { type: Number, default: 0 }
});
var counter = mongoose.model('counter', CounterSchema);
var entitySchema = mongoose.Schema({
sort: {type: String}
});
entitySchema.pre('save', function(next) {
var doc = this;
counter.findByIdAndUpdateAsync({_id: 'entityId'}, {$inc: { seq: 1} }, {new: true, upsert: true}).then(function(count) {
console.log("...count: "+JSON.stringify(count));
doc.sort = count.seq;
next();
})
.catch(function(error) {
console.error("counter error-> : "+error);
throw error;
});
});
refer https://stackoverflow.com/a/40137723/8201020

Document must have an _id before saving

I know this is an error that has been asked about several times on StackOverflow, but I can't seem to find the answer I'm looking for. I have a simple schema that stores an _id and a URL. The URL works fine, but when I go to create a new schema and save it, it states the above error even though I have prehooks to explicitly define the _id.
Here's the Schema code as well as the prehook:
const LinkSchema = new Schema({
_id: { type: Number },
url: { type: String, required: true }
}, {
timestamps: true,
collection: 'links'
});
LinkSchema.pre('save', function(next) {
// Before saving, increment the count in the linkEntryCount document in the counter collection and create the doc if not already made.
CounterModel.findByIdAndUpdate('linkEntryCount', { $inc: { count: 1 } }, { new: true, upsert: true, useFindAndModify: false }, function(err, counter) {
if(err) return next(err);
this._id = counter.count; // Create the previously undefined ObjectID with the +1'ed counter from linkEntryCount
next();
})
});
I've created an incrementing integer counter as per the MongoDB database - using a separate collection for counting. I've tested this and it works fine, and it even seems to assign the _id when the prehook is called. When I create an instance of the model and insert the URL, that's when the error appears. The document isn't even created.
Thanks for your help!
_id: { type: Number },
A mongodb _id isnt a number but a mongoose.Schema.Types.ObjectId
So replace that line with
_id: { type: mongoose.Schema.Types.ObjectId },
You should use _id only with
new mongoose.Types.ObjectId()
And then you can add like id: { type: Number } and use that as the counter
So I solved my question using this post.
The problem was that this was being used in the wrong context inside the CounterModel.findByIdAndUpdate() function. The code was trying to update a nonexistent field in the linkEntryCount collection.
Here's the fixed prehook:
LinkSchema.pre('save', function(next) {
var link = this;
// Before saving, increment the count in the linkEntryCount document in the counter collection and create the doc if not already made.
CounterModel.findByIdAndUpdate('linkEntryCount', { $inc: { count: 1 } }, { new: true, upsert: true, useFindAndModify: false }, function(err, counter) {
if(err) return next(err);
link._id = counter.count; // Create the previously undefined ObjectID with the +1'ed counter from linkEntryCount
next();
});
});
All I did was set the prehooks reference to this to a variable and then used that later in the findByIdAndUpdate() function.

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.

How to properly do a Bulk upsert/update in MongoDB

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
});

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