Update multiple documents by id set. Mongoose - mongodb

I wonder if mongoose has some method to update multiple documents by id set. For example:
for (var i = 0, l = ids.length; i < l; i++) {
Element.update({'_id': ids[i]}, {'visibility': visibility} ,function(err, records){
if (err) {
return false;
} else {
return true;
};
});
};
What i want to know, that if mongoose can do something like this:
Element.update({'_id': ids}, {'visibility': visibility}, {multi: true} ,function(err, records){
if (err) {
return false;
}
});
where ids is an array of ids, like ['id1', 'id2', 'id3'] - sample array.
Same question for find.

Most probably yes. And it is called using $in operator in mongodb query for update.
db.Element.update(
{ _id: { $in: ['id1', 'id2', 'id3'] } },
{ $set: { visibility : yourvisibility } },
{multi: true}
)
All you need is to find how to implement $in in mongoose.

in updateMany function no need of { multi: true }
db.collectionName.updateMany(
{
_id:
{
$in:
[
ObjectId("your object id"),
ObjectId("your object id")
]
}
},
{
$inc: { quantity: 100 }
})
I want to add one more point, you can use $in to fetch multiple document
db.collectionName.find(
{
_id:
{
$in:
[
ObjectId("your object id"),
ObjectId("your object id")
]
}
})

Updates all documents that match the specified filter for a collection.
let ids = ["kwwe232244h3j44jg3h4", "23h2u32g2h3b3hbh", "fhfu3h4h34u35"];
let visibility = true;
Element.updateMany({_id: {$in: ids}},
{ $set: { visibility } },
{multi: true} ,
function(err, records){
if (err) {
return false;
}
});
know more

Related

How can I get mongo documents with multiple non-exclusive selectors using Mongoose?

I need to build a single query to return :
Documents from their IDs (optional)
Documents matching a text search string (optional)
The other documents sorted by score and with a count limited by an integer argument
The total limit is the provided one + the length of the documents array IDs of the first condition.
I used to work with meteor where you can return an array of queries cursors. In this case, I am working with a mongoose backend and I am not sure of how to proceed. I assume I need to use Model.aggregate and provide my conditions as an array. However, the request fails with the error Arguments must be aggregate pipeline operators.
Each of my conditions works fine individually with a regular find() query.
Here is my graphQL query resolver, where I can't find what is going wrong:
async (root, { search, selected = 0, limit = 10 }, { models: { tag } }) => {
try {
let selector = [{}] // {} should return the documents by default if no other condition is set
if (selected.length) selector.push({ _id: { $in: selected } })
if (search && search.length) selector.push({
$text: {
$search: search,
$caseSensitive: false,
$diacriticSensitive: false
}
})
const tags = await tag.aggregate(selector).sort('-score').limit(limit + selected.length)
return {
ok: true,
message: "Tags fetched",
data: tags
}
} catch (err) { return { ok: false, message: err.message }; }
}
),
When I log the selector with all the arguments set, it returns an array of the following form:
[
{},
{ _id: { '$in': [Array] } },
{
'$text': {
'$search': 'test',
'$caseSensitive': false,
'$diacriticSensitive': false
}
}
]
UPDATE
Based on #Ashh answer, with an additional $or operator, the full agregator variable look like this:
{
'$match': {
'$or': {
_id: {
'$in': [ '5e39745e0ac14b1731a779a3', '5e39745d0ac14b1731a76984' ]
},
'$text': {
'$search': 'test',
'$caseSensitive': false,
'$diacriticSensitive': false
}
}
}
},
{ '$sort': { score: -1 } },
{ limit: 12 }
I still get the "Arguments must be aggregate pipeline operators" error, and I don't see where, if the $text argument is not present, I get the default documents by score.
#Ashh, I'll wait for your updated answer to validate it. Thanks again for your help.
Mongoose aggregate() function uses $match stage which is equivalent to the find() but accepts some stages as array of elements to filter the documents. You can check the example here Mongoose Aggregate.
And rest is your code fault. It should be
async (root, { search, selected = 0, limit = 10 }, { models: { tag } }) => {
try {
const aggregate = []
let selector = { $match: { }};
aggregate.push(selector)
if (selected.length) {
aggregate[0].$match['$or'] = [];
aggregate[0].$match.$or.push({ _id: { $in: selected }});
}
if (search && search.length) {
aggregate[0].$match['$or'] = aggregate[0].$match['$or'] ? aggregate[0].$match['$or'] : []
aggregate[0].$match.$or.push({ $text: {
$search: search,
$caseSensitive: false,
$diacriticSensitive: false
}})
}
aggregate.push({ $sort: { score: - 1 }})
aggregate.push({ $limit: limit })
const tags = await tag.aggregate(aggregate)
return {
ok: true,
message: "Tags fetched",
data: tags
};
} catch (err) {
return { ok: false, message: err.message };
}
};

Update Multiple Sub Doc By array of sub doc _id's in mongodb

I am trying to update multiple sub documents by given array of sub documents id's. I tried multiple approaches but it's not working.
In my scenario i need to update multiple sub documents by given array of id's. Here is my query as below:
Approach 1. (No elements were updating)
var updated = await ModelName.update(
{
'subDocArray._id' : { $in: req.body.elementId }
},
{
$set: {
'subDocArray.$[elem].abc': req.body.abcValue,
'subDocArray.$[elem].xyz': req.body.xyzValue
},
},{ "arrayFilters": [{ "elem._id": { $in: req.body.elementId } }], "multi": true, "upsert": true }
).lean().exec();
Approach 2: (Only First occurred element is updating)
var updated = await ModelName.update(
{
'subDocArray._id' : { $in: req.body.elementId }
},
{
$set: {
'subDocArray.$.abc': req.body.abcValue,
'subDocArray.$.xyz': req.body.xyzValue
},
},{ multi: true}
).exec();
Here req.body.elementId is array of sub doc id's.
Approach 1 was almost right. I was passing array of elementId's are which are in string format so i converted them in ObjectId form and then it works.
var arrOfObjectId = [];
req.body.elementId.forEach(elem => {
arrOfObjectId.push(Types.ObjectId(elem))
});
To find the difference between both of the array i printed both in console which were showing like below:
console.log(req.body.elementId)
Result: ['xxxxxxxxxxxxxxxxxxxxxxxx','yyyyyyyyyyyyyyyyyyyyyyyy'] //WRONG
console.log(arrOfObjectId)
Result: [ObjectId('xxxxxxxxxxxxxxxxxxxxxxxx'),ObjectId('yyyyyyyyyyyyyyyyyyyyyyyy')] //RIGHT
var updated = await ModelName.update(
{
'subDocArray._id' : { $in: arrOfObjectId }
},
{
$set: {
'subDocArray.$[elem].abc': req.body.abcValue,
'subDocArray.$[elem].xyz': req.body.xyzValue
},
},{ "arrayFilters": [{ "elem._id": { $in: arrOfObjectId } }], "multi": true, "upsert": true }
).lean().exec();

Insert or update many documents in MongoDB

Is there a way to insert or update/replace multiple documents in MongoDB with a single query?
Assume the following collection:
[
{_id: 1, text: "something"},
{_id: 4, text: "baz"}
]
Now I would like to add multiple documents of which some might already be in the collection. If the documents are already in the collection, I would like to update/replace them. For example, I would like to insert the following documents:
[
{_id:1, text: "something else"},
{_id:2, text: "foo"},
{_id:3, text: "bar"}
]
The query should insert the documents with _id 2 and 3. It should also update/replace the document with _id 1. After the process, the collection should look as follows:
[
{_id:1, text: "something else"},
{_id:2, text: "foo"},
{_id:3, text: "bar"},
{_id:4, text: "baz"}
]
One approach might be to use insertMany:
db.collection.insertMany(
[ {...}, {...}, {...} ],
{
ordered: false,
}
)
If duplicates occur, that query will emit a writeErrors containing an array of objects containing the indexes of the documents that failed to insert. I could go through them and update them instead.
But that process is cumbersome. Is there a way to insert or update/replace many documents in one query?
As said here, to do what you need you can put something like this in
script.js
(* warning: untested code)
use YOUR_DB
var bulk = db.collection.initializeUnorderedBulkOp();
bulk.find( { _id : 1 } ).upsert().update( { $set: { "text": "something else" } } );
bulk.find( { _id : 4 } ).upsert().update( { $set: { "text": "baz" } } );
bulk.find( { _id : 99 } ).upsert().update( { $set: { "text": "mrga" } } );
bulk.execute();
and run it with
mongo < script.js
I had to do it this way as anything I tried for updating/inserting more than 1000 documents didn't work because of the limit.
Write commands can accept no more than 1000 operations. The Bulk() operations in the mongo shell and comparable methods in the drivers do not have this limit.
source
You can also use bulkWrite api to update or insert multiple documents in a single query, here is an example
var ops = []
items.forEach(item => {
ops.push(
{
updateOne: {
filter: { _id: unique_id },
update: {
$set: { fields_to_update_if_exists },
$setOnInsert: { fileds_to_insert_if_does_not_exist }
},
upsert: true
}
}
)
})
db.collections('collection_name').bulkWrite(ops, { ordered: false });
Considering data item as follows:
interface Item {
id: string; // unique key across collection
someValue: string;
}
If you have items under the limit 1000, you can make bulk write operation like this:
public async insertOrUpdateBulk(items: Item[]) {
try {
const bulkOperation = this._itemCollection.initializeUnorderedBulkOp();
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
const item = items[itemIndex];
bulkOperation.find({ id: item.id }).upsert().update(item);
}
await bulkOperation.execute();
return true;
} catch (err) {
console.log(err);
return false;
}
}
If you have items that exceed the limit 1000, you can make simultaneous promises:
public async insertOrUpdate(items: Item[]) {
try {
const promises: Array<Promise<UpdateWriteOpResult>> = [];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
const item = items[itemIndex];
const updatePromise = this.itemCollection.updateOne({ id: item.id }, item, { upsert: true });
promises.push(updatePromise);
}
await Promise.all(promises);
console.log('done...');
return true;
} catch (err) {
console.log(err);
return false;
}
}

Mongoose elemMatch and inc query based on id

How am I going to write mongoose function that will query this mongo query based on a given userId (I could not add this to query, also need that).
db.users.update({ categories : { $elemMatch: { name: "Sport" } } },
{$inc: {"categories.$.points": 6666, points : 7777}})
That doesn't help me out.
User.findByIdAndUpdate(
request.params.userId,
//{ $inc: { points: 1 }},
{ categories : { $elemMatch: { name: "Spor" }}},
{$inc: {"categories.$.points": 6666, points : 7777}},
//{ safe: true, upsert: true, new : true },
function(err, user) {
if (!err) {
return reply(user); // HTTP 201
}
if (11000 === err.code || 11001 === err.code) {
return reply(Boom.forbidden("please provide another user id, it already exist!"));
}
return reply(Boom.forbidden(err)); // HTTP 403
}
);
You need to use findOneAndUpdate instead of findByIdAndUpdate if you want to match on anything beyond just the _id. You also don't need to use $elemMatch here as you're only matching against a single field in the categories array so a simple dot notation field match can be used instead.
So it should be:
User.findOneAndUpdate(
{ _id: request.params.userId, "categories.name": "Sport" },
{ $inc: { "categories.$.points": 6666, points : 7777 }},
{ safe: true, upsert: true, new: true },
function(err, user) { ... }
);

How to convert string to boolean in MongoDB?

I am new to mongo so excuse me if it's a noobish question. I have wrongfully updated a specific flag in mongo with "true"/"false" (type strings). I want a query so that I could update my collection and change the type of the flag from string "true" to boolean true.
Example:
{flag: "true"} to { flag : true}
So I have 2 questions:
Can I do that with a query?
If I can, how?
For relatively small collections, perform an update if the type of field is string:
db.collection.find({ "flag": { $type : 2 } }).forEach(function (doc){
var isTrueSet = (doc.flag === "true");
doc.flag = isTrueSet; // convert field to Boolean
db.collection.save(doc);
});
For medium/large collections you can use the bulkWrite API as
var cursor = db.collection.find({ "flag": { "$exists": true, "$type": 2 } }),
ops = [];
cursor.forEach(function(doc){
var isTrueSet = (doc.flag === "true");
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": { "$set": { "flag": isTrueSet } }
}
});
if (ops.length == 1000) {
db.collection.bulkWrite(ops);
ops = [];
}
});
if (ops.length > 0) { db.collection.bulkWrite(ops); }
Just call these two queries:
db.coll.update({
flag: "true"
}, {
$set: {flag: true}
}, { multi: true })
db.coll.update({
flag: "false"
}, {
$set: {flag: false}
}, { multi: true })
First one changes all "true" to true, second you will get it.
For a medium/big collection this will work significantly faster than already suggested foreach statement.
See MongoDB Manual - Modify Documents. Example:
db.collectionName.update({_id: "yourid"}, {$set: {flag : true}})
Strings can be converted to boolean in MongoDB v4.0 using $toBool operator. In this case
db.col.aggregate([
{
$project: {
_id: 0,
flagBool: { $toBool: "$flag" }
}
}
])
Outputs:
{ "flagBool" : true }