Upsert to many subdocuments in one database call - javascript

If I have a document with an array of objects.
Document = {
_id: 1,
doc_amount: 0,
subDocsArray: [
{
_id: 1,
amount: 0
},
{
_id: 2,
amount: 1
},
...
]
}
I can increment the doc_amount simply by doing...
Document.updateOne({ _id }, { $inc: { doc_amount: 1} })
But incrementing the amounts in subDocsArray is more tricky. If I have an array of updates for Document.subDocsArray
var newSubDocsArray = [
{
_id: 1,
amount: 5
},
{
_id: 2,
amount: 5
},
]
How would I go about incrementing all amounts in Document.subDocsArray in one call? Bearing in mind that the newSubDocsArray may not always contain every item in subDocsArray so would need to upsert.
Is that even possible?
EDIT:
To clear something up, all the amounts in newSubDocsArray could be different values. Each existing subDoc array item amount should increment by the value of the amount in newSubDocs with the same id.

As I not really answered the question in previous answer, here's a new one.
Afaik, you cannot do this in one call in Mongodb, but you need a call for each new array element. Then you can use arrayFilters to update all your items matching current element. Here's an example with different behaviours :
Your original document :
{
"_id" : 1.0,
"doc_amount" : 0.0,
"subDocsArray" : [
{
"_id" : 1.0,
"amount" : 20.0
},
{
"_id" : 2.0,
"amount" : 31.0
},
{
"_id" : 3.0,
"amount" : 3.0
},
{
"_id" : 5.0
}
]
}
Applying this script :
var newSubDocsArray = [
{
_id: 1,
amount: 3
},
{
_id: 2,
amount: 8
},
{
_id: 4,
amount: 7
},
{
_id: 5,
amount: 9
},
];
newSubDocsArray.forEach(function(newSubDoc){
db.getCollection("test").update(
{_id:1},
{$inc:{"subDocsArray.$[subdoc].amount":newSubDoc.amount}}, // Note the use of $[subdoc]
{ multi: true,
arrayFilters: [ { "subdoc._id": newSubDoc._id } ]
}
)
})
Will result :
{
"_id" : 1.0,
"doc_amount" : 0.0,
"subDocsArray" : [
{
"_id" : 1.0,
"amount" : 23.0 // +3
},
{
"_id" : 2.0,
"amount" : 39.0 // +8
},
{
"_id" : 3.0,
"amount" : 3.0 // unchanged
},
{
"_id" : 5.0,
"amount" : 9.0 // created, set to 9
}
]
}
You can note that :
item with _id 3 is not updated (not in new newSubDocsArray).
item with _id 4 is NOT created in the array (arrayFilters does not match anything).
items with _id 5 has a new amount field created.

You can use the $[] array update oprator to achieve this :
db.getCollection("test").update(
{_id:1},
{$inc:{"subDocsArray.$[].amount":1}}
)

You can updated the sub array item with $ like
give the criteria first { _id: 1,'subDocsArray._id' : 1}
then update items like : {$inc :{'subDocsArray.$.amount':1}}

Related

Is it possible to update multiple documents with different values using mongo? [duplicate]

I have the following documents:
[{
"_id":1,
"name":"john",
"position":1
},
{"_id":2,
"name":"bob",
"position":2
},
{"_id":3,
"name":"tom",
"position":3
}]
In the UI a user can change position of items(eg moving Bob to first position, john gets position 2, tom - position 3).
Is there any way to update all positions in all documents at once?
You can not update two documents at once with a MongoDB query. You will always have to do that in two queries. You can of course set a value of a field to the same value, or increment with the same number, but you can not do two distinct updates in MongoDB with the same query.
You can use db.collection.bulkWrite() to perform multiple operations in bulk. It has been available since 3.2.
It is possible to perform operations out of order to increase performance.
From mongodb 4.2 you can do using pipeline in update using $set operator
there are many ways possible now due to many operators in aggregation pipeline though I am providing one of them
exports.updateDisplayOrder = async keyValPairArr => {
try {
let data = await ContestModel.collection.update(
{ _id: { $in: keyValPairArr.map(o => o.id) } },
[{
$set: {
displayOrder: {
$let: {
vars: { obj: { $arrayElemAt: [{ $filter: { input: keyValPairArr, as: "kvpa", cond: { $eq: ["$$kvpa.id", "$_id"] } } }, 0] } },
in:"$$obj.displayOrder"
}
}
}
}],
{ runValidators: true, multi: true }
)
return data;
} catch (error) {
throw error;
}
}
example key val pair is: [{"id":"5e7643d436963c21f14582ee","displayOrder":9}, {"id":"5e7643e736963c21f14582ef","displayOrder":4}]
Since MongoDB 4.2 update can accept aggregation pipeline as second argument, allowing modification of multiple documents based on their data.
See https://docs.mongodb.com/manual/reference/method/db.collection.update/#modify-a-field-using-the-values-of-the-other-fields-in-the-document
Excerpt from documentation:
Modify a Field Using the Values of the Other Fields in the Document
Create a members collection with the following documents:
db.members.insertMany([
{ "_id" : 1, "member" : "abc123", "status" : "A", "points" : 2, "misc1" : "note to self: confirm status", "misc2" : "Need to activate", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") },
{ "_id" : 2, "member" : "xyz123", "status" : "A", "points" : 60, "misc1" : "reminder: ping me at 100pts", "misc2" : "Some random comment", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") }
])
Assume that instead of separate misc1 and misc2 fields, you want to gather these into a new comments field. The following update operation uses an aggregation pipeline to:
add the new comments field and set the lastUpdate field.
remove the misc1 and misc2 fields for all documents in the collection.
db.members.update(
{ },
[
{ $set: { status: "Modified", comments: [ "$misc1", "$misc2" ], lastUpdate: "$$NOW" } },
{ $unset: [ "misc1", "misc2" ] }
],
{ multi: true }
)
Suppose after updating your position your array will looks like
const objectToUpdate = [{
"_id":1,
"name":"john",
"position":2
},
{
"_id":2,
"name":"bob",
"position":1
},
{
"_id":3,
"name":"tom",
"position":3
}].map( eachObj => {
return {
updateOne: {
filter: { _id: eachObj._id },
update: { name: eachObj.name, position: eachObj.position }
}
}
})
YourModelName.bulkWrite(objectToUpdate,
{ ordered: false }
).then((result) => {
console.log(result);
}).catch(err=>{
console.log(err.result.result.writeErrors[0].err.op.q);
})
It will update all position with different value.
Note : I have used here ordered : false for better performance.

MongoDB expireAfterSeconds doesn't remove documents

I would like that MongoDB clears data from its collections after %seconds pass. I am setting index, but collection doesn't gets cleared after a while, all the documents are still present.
What am I doing wrong?
DB Version: 3.2
Setting Index:
db.collection('history').ensureIndex(
{ '_id': 1, 'created': 1 },
{ unique: true, background: true, w: 1, expireAfterSeconds: 60}
);
// or
db.collection('history').createIndex(
{ '_id': 1, 'created': 1 },
{ unique: true, background: true, w: 1, expireAfterSeconds: 60}
);
// history document
var _history = {
_id: new ObjectId(),
created: new Date()
};
Collection history, index:
var historyCollectionIndex = [
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "guardian_dev.history"
},
{
"v" : 1,
"unique" : true,
"key" : {
"_id" : 1,
"created" : 1
},
"name" : "_id_1_created_1",
"ns" : "guardian_dev.history",
"background" : true,
"expireAfterSeconds" : 60
}
]
Additional question that is connected to creating indexes.
Now, it can happen that two entries have the same created value, and because of this, mongo is now throwing an error of E11000 duplicate key error collection.
Is it possible to add created and expireAfterSeconds, but created doesn't have to be uniq?
According to the MongoDB site:
The TTL index is a single field index. Compound indexes do not support the TTL property.
If you remove the _id: 1 index and instead just use created then it should behave as you expect
According to the documentation, the TTL index is a single field index. Compound indexes do not support the TTL property. You should create the index as follows:
db.collection('history').ensureIndex(
{'created': 1 },
{ unique: true, background: true, w: 1, expireAfterSeconds: 60}
);
I have tested it and this index, unlike the one in your question, clears out the records correctly.

How to sort inner array and match elements in mongoose

So I am trying to do a semi-complicated query in mongoose. It is as follows:
Event.findOne({ users: { $elemMatch: { user: someUserId, going: 1 }, sort: {createTime: -1} } }
Basically what I would like to do is find a single Event that has a user in its user array that matches an Id and is attending. I have multiple user records for each user in the user array so I want to find the most recent one, to do this I would like to sort the user array by createTime. This is where the error is coming in and it just returns undefined. It works just fine when I don't include the sort function, is there any way to include that?
Here is what my Event object looks like:
{
_id: 1,
endTime: 1429060173865,
createTime: 1429051773902,
startTime: 1429052973865,
description: 'some desc',
creator: 2,
users:
[ { user: 1,
status: 1,
going: 1,
createTime: 1429051773749,
_id: 552d997d8e923847306e2c21 },
{ user: 1,
status: 1,
going: 1,
createTime: 1429051773922,
_id: 552d997d8e923847306e2c25 },
{ user: 1,
status: 9,
going: 0,
createTime: 1429051773942,
_id: 552d997d8e923847306e2c26 } ],
destroyed: 0 }
Is there any way to make this query entirely in mongoose?
As part of find, MongoDB can't sort an array field of a document. You could define a virtual of Mongoose to return the array in sorted order. You could also maintain the array in sorted order, as shown below:
> db.test.drop()
// note: not in order when inserted
> db.test.insert({ "_id" : 0, "users" : [
{ "user" : 1, "going" : 1, "created" : 22 },
{ "user" : 2, "going" : 1, "created" : 775 },
{ "user" : 1, "going" : 1, "created" : 6432 }
] })
// insert user to array and sort as part of update
> db.test.update({ "_id" : 0 },
{ "$push" : {
"users" : {
"$each" : [{ "user" : 2, "going" : 1, "created" : 5532 }],
"$sort" : { "created" : -1 }
}
} })
> > db.test.findOne()
{
"_id" : 0,
"users" : [
{ "user" : 1, "going" : 1, "created" : 6432 },
{ "user" : 2, "going" : 1, "created" : 5532 },
{ "user" : 2, "going" : 1, "created" : 775 },
{ "user" : 1, "going" : 1, "created" : 22 }
]
}
That way, when you perform your find query, the arrays in the matching documents will already be in the desired order.
Your query is not correctly written. In order to sort you should write it in third argument of find:
Event.findOne(
{ users:
{ $elemMatch: { user: someUserId, going: 1 }}
},
null,
{
sort: {createTime: -1}
},
function(err, event) {
//your event here
});

Sort collections of docs By the biggest embedded Doc with mongodb

I have this schema with mongoose
schema = new Schema({
id: {
type: String,
},
embedded: [embeddedSchema]
});
embeddedSchema = new Schema({
value: {
type: String,
},
});
This can produce something like :
{
"_id" : ObjectId("5454f4f1073cc3b529320f79"),
"embedded" : [
{
"value" : 123,
} , {
"value" : 123,
},
{
"value" : 123423,
}
]
}
/* 1 */
{
"_id" : ObjectId("5454f508910ef3b82970f11d"),
"embedded" : [
{
"value" : 1,
} , {
"value" : 2,
},
{
"value" : 9999999,
}]
}
I would like to sort the schema collection by the biggest value of embedded doc.
Which query can produce this kind of result ?
Thanks you!
When you sort descending on an array element field like value, MongoDB uses the maximum value of that field among all elements in the array.
So in this case it would be:
MyModel.find().sort('-embedded.value').exec(callback);

MongoDB, remove object from array

Doc:
{
_id: 5150a1199fac0e6910000002,
name: 'some name',
items: [{
id: 23,
name: 'item name 23'
},{
id: 24,
name: 'item name 24'
}]
}
Is there a way to pull a specific object from an array? I.E. how do I pull the entire item object with id 23 from the items array.
I have tried:
db.mycollection.update({'_id': ObjectId("5150a1199fac0e6910000002")}, {$pull: {id: 23}});
However I am pretty sure that I am not using 'pull' correctly. From what I understand pull will pull a field from an array but not an object.
Any ideas how to pull the entire object out of the array.
As a bonus I am trying to do this in mongoose/nodejs, as well not sure if this type of thing is in the mongoose API but I could not find it.
try..
db.mycollection.update(
{ '_id': ObjectId("5150a1199fac0e6910000002") },
{ $pull: { items: { id: 23 } } },
false, // Upsert
true, // Multi
);
I have a document like
I have to delete address from address array
After searching lots on internet I found the solution
Customer.findOneAndUpdate(query, { $pull: {address: addressId} }, (err, data) => {
if (err) {
return res.status(500).json({ error: 'error in deleting address' });
}
res.json(data);
});
my database:
{
"_id" : ObjectId("5806056dce046557874d3ab18"),
"data" : [
{ "id" : 1 },
{ "id" : 2 },
{ "id" : 3 }
]
}
my query:
db.getCollection('play_table').update({},{$pull:{"data":{"id":3}}},{multi:true}
output:
{
"_id" : ObjectId("5806056dce046557874d3ab18"),
"data" : [
{ "id" : 1 },
{ "id" : 2 }
]
}
You can try it also:
db.getCollection('docs').update({ },{'$pull':{ 'items':{'id': 3 }}},{multi:true})
For a single record in array:
db.getCollection('documents').update(
{ },
{'$pull':{ 'items':{'mobile': 1234567890 }}},
{new:true}
);
For a multiple records with same mobile number in array:
db.getCollection('documents').update(
{ },
{
$pull: {
items: { mobile: 1234567890 }
}
},
{ new:true, multi:true }
)
Use $pull to remove the data
return this.mobiledashboardModel
.update({"_id": args.dashboardId}, { $pull: {"viewData": { "_id": widgetId}}})
.exec()
.then(dashboardDoc => {
return {
result: dashboardDoc
}
});
Kishore Diyyana:
If you want to remove all elements including the key of the element attributes list.
Here is the example of mongoDB unset operator:
db.UM_PREAUTH_CASE.update(
{ 'Id' : 123}, { $unset: { dataElements: ""} } )
JSON Look like this:
{ "Id":123,"dataElements" : [ { "createdBy" : "Kishore Babu Diyyana", "createdByUserId" : 2020 }, { "createdBy" : "Diyyana Kishore", "createdByUserId" : 2021 } ] }

Categories

Resources