MongoDB Update from Aggregate - mongodb

I am extremely new to Mongo and need some up creating an update statement. I have two different collections. I need to update the one collection's values with the results from my aggregate query where the id's match.
Here is my aggregate query that gives me the id for the other collection and the value I need to set it to:
db.ResultsCollection.aggregate(
{$group:{_id:"$SystemId", "maxValue": {$max:"$LastModified"}}}
);
How do I loop through the other collection with this data and update where the _id matches the SystemId from my aggreagate?
UPDATED CODE:
db.ResultsCollection.aggregate(
{$group:{_id:"$SystemId", "maxValue": {$max:"$LastModified"}}}
).forEach(function(
db.CollectionToUpdate.updateOne(
{ _id : doc._id },
{ $set: {UpdateDate: doc.maxValue } },
{ upsert: false }
);
});
My updated code does not generate a syntax error, but does not update the results when I refresh.

Related

MongoDB upsert $in

I have a bunch of records I want to upsert for specific product ids. Depending on previous calculations I want to record what type that product was in the current week/year.
Problem is that I can't figure out a way to do this except for one at a time. Right now I'm doing:
a_group.forEach(p => {
db.abc.update({
product_id: p._id,
year: 2021
}, {
$set: {
'abc.34': 'a'
}
}, {
upsert: true
});
});
Where a_group is just an array of products.
This is really heavy in case of a large products array. It just does a_group.length upsert operations.
Ideally I would like to do something like:
db.abc.update({
product_id: { $in: a_group.map(p => p._id) },
year: 2021
}, {
$set: {
'abc.34': 'a'
}
}, {
upsert: true,
multi: true
});
Which would see that a_group is an array and try to match and upsert for every single item in the array. Except that doesn't work.
Any help would be very much appreciated.
The problem here is you want a separate upsert for each discreet value of the _id.
From the docs:
An upsert:
Updates documents that match your query filter
Inserts a document if there are no matches to your query filter
In the case of an upsert such as:
updateMany(
{a:{$in[1,2,3]}},
{$set:{b:true}},
{upsert: true}
)
If there exists a document containing a: 3, then it will match the query filter, and therefore that one document will be updated, and no inserts will occur.
In the event that no document matches any of the values passed to $in, a single new document will be upserted. Since the query has no way to determine which value of a you wanted, it will create a document containing {b:true}, but will leave a undefined.
What you probably want is a bulk operation that can perform many upsert operations with a single call to the database.
Using the mongosh shell, that might look like:
let ops = [];
a_group.forEach(p => {
ops.push(
{ updateOne:
{
filter: {
product_id: p._id,
year: 2021
},
update: {$set: {'abc.34': 'a'}},
upsert: true
}
);
});
db.abc.bulkWrite(ops)
Check the docs for the driver you are using to see how to do a bulk operation.
according to your code i think a_group is array of object because you use p._id in forEach ,
you should exports _id from a_group and push in new array for example productsId
and replace
product_id: { $in: a_group }
with
product_id: { $in: productsId },

MongoDB - Update One Field or Another

I'm pretty new to MongoDB so this might be my inexperience with it. I'm trying to do an upsert that when a record is found it will update multiple fields based on multiple conditions.
I have the following record in a collection:
{
modelId: "5e68c7eaa0887971ea6ef54c",
versionId: 999,
timeStamp: "1/1/2020",
oldValue: 'Blue',
newValue: 'Red'
}
I'm trying to satisfy the following conditions with a single upsert statement in order to avoid making multiple trips to the DB (based on the query that a document matching the modelId and versionId is found:
If timeStamp of new record is before (lt) the existing document then update oldValue
If timeStamp of new record is after (gt) the existing document then update newValue
If matching records is not found insert the new record.
In psuedo code terms I'm trying to do this with the upsert statement:
existingRecord = item in collection matching modelId and versionId
if(existingRecord = null)
{
//insert newRecord
}
if(newRecord.timeStamp < existingRecord.timeStamp)
{
existingRecord.oldValue = newRecord.oldValue
existingRecord.timeStamp = newRecord.timeStamp
}
else if(newRecord.timeStamp > existingRecord.timeStamp)
{
existingRecord.newValue = newRecord.newValue
existingRecord.timeStamp = newRecord.timeStamp
}
I've seen the possibility to do an upsert based on the condition of a date, something like:
db.collection.update( { id:o.id, date: { $lt:o.date } }, {$set : { o }}, {upsert:true} );
I don't know how to expand that to be able to update either the oldValue or the newValue based on the timeStamp value.
I'm planning on having a good amount of records inserted into the collection every day, estimate around 1MM, I'd hate to have to do a find() and then an update() for each record.
I'm using Mongo 4.0 and would appreciate any advice.
Thanks!
Well, in version 4.0, you are not allowed to use the conditions in the update query. Hence, you end up firing two queries instead.
db.collection.update({condition}, { $set: { o } }, { multi: true ,upsert:true });
db.collection.update({!condition}, { $set: { n } }, { multi: true ,upsert:true });
However, in version 4.2, added db.collection.update pipeline, in which the aggregation is allowed.
And, it contains only the following aggregation stages:
$addFields and its alias $set
$project and its alias $unset
$replaceRoot and its alias $replaceWith.
Hope this will help :)
Update
I have added the $set stage to update the document. It will update the if timestamp condition is true else it will not update. and applies the same for other condition.
I have used the long value of timestamp you can use according to you case.
db.collection.update(
{
modelId: "5e68c7eaa0887971ea6ef54c",
versionId: 999,
},
[
{
$set:{
"oldValue":{
$cond:[
{
$lt:[
"timestamp",
1598598257000
]
},
"green",
"$oldValue"
]
}
}
},
{
$set:{
"newValue":{
$cond:[
{
$gt:[
"timestamp",
1518598257000
]
},
"pink",
"$newValue"
]
}
}
}
]
)

MongoDB: Filter on _id == obj._id without setting obj._id to null on insert

I'd like to configure an upsert. If _id already exists on my object, it recognizes that and updates the match. If _id doesn't exist, it should insert the new document and generate an _id. I'd expect { _id: obj._id } to work, but that overrides the auto-generation of _id. The document appears with _id: null. Is there a filter that would work for this? Do I need to route to insert/update in-code?
Edit: add query.
collection.updateOne(
{ _id: entity._id },
{ $set: entity },
{ upsert: true }
)
Edit: try delete.
Even when I delete the property, no luck.
const upsertTest = (collection) => {
const entity = { date: new Date() };
console.log(entity);
// { date: 2019-11-19T22:16:00.914Z }
const _id = entity._id;
delete entity._id;
console.log(entity);
// { date: 2019-11-19T22:16:00.914Z }
collection.updateOne(
{ _id: _id },
{ $set: entity },
{ upsert: true }
);
console.log(entity);
// { date: 2019-11-19T22:16:00.914Z }
}
But the new document is this:
screenshot of new document
You have to use $setOnInsert to Insert _id
let ObjectID = require('mongodb').ObjectID;
collection.updateOne(
{ _id: entity._id },
{ $set: entity,$setOnInsert:{_id:new ObjectID()}},
{ upsert: true }
)
As the name suggests it will set the _id on insert
If you are using mongodb then new ObjectID() should work,
If its mongoose then you can use mongoose.Types.ObjectId() to generate new ObjectID
Well I Found your Issue
Changed in version 3.0: When you execute an update() with upsert: true and the query matches no existing document, MongoDB will refuse to insert a new document if the query specifies conditions on the _id field.
So in a nutshell you cannot insert a new doc using upsert:true if your query is using _id, Reference
First of all, the _id property WILL always exist, whether you set it yourself or let it auto-generate. So there's no need to check if it exists.
The syntax for upsert is as follows:
db.collection.update({
_id: 'the id you want to check for' // can put any valid 'find' query here
}, {
$set: {
foo: 'bar',
biz: 'baz'
}
}, {
upsert: true
});
If a document with the given _id exists, it will be updated with the $set object. If it does not exist, a new one will be created with the properties of the $set object. If _id is not specified in the $set object, a new _id value will be auto-generated, or will keep using the existing _id value.
The key here is using $set, rather than putting the object right in there. Using set will merge and replace the properties. Without $set it will replace the entire document, removing any unset properties.
Edit: Now seeing the actual query you made, I would suggest you delete the _id property from the entity before setting it. This will make sure it is left alone.
const _id = entity._id;
delete entity._id;
// now do your query using the new `_id` value for the ID.
From our comments you mentioned you were using the local database. The local database allows _id to be null. Using any other DB should fix your issue.
If the entity._id is null then it will create a doc with _id null, We can solve this issue by adding Types.ObjectId(). If the _id doesn't exist then it will create a new document with the proper _id. otherwise, it will update the existing document.
const { Types } = require("mongoose")
collection.updateOne({ _id: Types.ObjectId(entity._id) },{ $set: entity },{ upsert: true})

How to update/insert into a mongodb collection with the result of aggregate query on the another collection

I have a below aggregate query and I wanted to insert/update this result into another collection.
db.coll1.aggregate([
{
$group:
{
_id: "$collId",
name: {$first:"$name"} ,
type: {$first:"$Type"} ,
startDate: {$first:"$startDate"} ,
endDate: {$first:"$endDate"}
}
}
])
I have another collection coll2, which has fields in the above query and some additional fields too.
I may already have the document created in Coll2 matching the _id:collId in the above query. If this Id matches, I want to update the document with the field values of the result and keep the values of other fields in the document.
If the Id does not exists, it should just create a new document in Coll2.
Is there a way to do it in the MongoDB query. I want to implement this in my Spring application.
We can use $merge to do that. It's supported from Mongo v4.2
db.collection.aggregate([
{
$group:{
"_id":"$collId",
"name": {
$first:"$name"
},
"type":{
$first:"$Type"
},
"startDate":{
$first:"$startDate"
},
"endDate":{
$first:"$endDate"
}
}
},
{
$merge:{
"into":"collection2",
"on":"_id",
"whenMatched":"replace",
"whenNotMatched":"insert"
}
}
])

MongoDB update collection's data

I try to update an MongoDB data by using this code:
db.medicines.update({"_id":"586a048e34e5c12614a7424a"}, {$set: {amount:'3'}})
but unfortantly the query does not recognize the selector "_id":"586a048e34e5c12614a7424a", even if its exists.
Its succsed when I change the key to another like: name,rate and etc..
there is a special way to use update with _id parameter?
Thanks a head.
_id will be the unique ObjectId that mongodb generates for every document before inserting it. The query dint work because _id is an ObjectId and "586a048e34e5c12614a7424a" is a String. You need to wrap _id with ObjectId().
If you're using mongodb query
db.medicines.update({
"_id": ObjectId("586a048e34e5c12614a7424a")
}, {
$set: {
amount: '3'
}
});
If you are using mongoose. You can use findByIdAndUpdate
db.medicines.findByIdAndUpdate({
"_id": "586a048e34e5c12614a7424a"
}, {
$set: {
amount: '3'
}
});