I have a document
"watched": {
"1": true,
"2": true, //should add 3 with true value
},
"fans": 4 //should increment by 5 it should be 9
when ever I do update it should increase the key and value and update the fans increment by 5
db.getCollection('movies').updateOne({
_id: Object_id("60e80c96b9c55e7a01898f5c")
}, { $set: { "watched.3": true} } },
{ $inc: { fans: 5 } }
)
EITHER it does update or increment how to manage both
db.collection.update({
_id: ObjectId("60e80c96b9c55e7a01898f5c"),
"watched.3": {
$exists: false
}
},
{
$set: {
"watched.3": true
},
$inc: {
fans: 5
}
})
mongoplayground
Related
I am trying to run a MongoDB query to update the value of one field with the value of another nested field. I have the following document:
{
"name": "name",
"address": "address",
"times": 10,
"snapshots": [
{
"dayTotal": 2,
"dayHit": 2,
"dayIndex": 2
},
{
"dayTotal": 3,
"dayHit": 3,
"dayIndex": 3
}
]
}
I am trying like this:
db.netGraphMetadataDTO.updateMany(
{ },
[{ $set: { times: "$snapshots.$[elem].dayTotal" } }],
{
arrayFilters: [{"elem.dayIndex":{"$eq": 2}}],
upsert: false,
multi: true
}
);
but got an error:
arrayFilters may not be specified for pipeline-syle updates
You can't use arrayFilters with aggregation pipeline for update query at the same time.
Instead, what you need to do:
Get the dayTotal field from the result 2.
Take the first matched document from the result 3.
Filter the document from snapshots array.
db.netGraphMetadataDTO.updateMany({},
[
{
$set: {
times: {
$getField: {
field: "dayTotal",
input: {
$first: {
$filter: {
input: "$snapshots",
cond: {
$eq: [
"$$this.dayIndex",
2
]
}
}
}
}
}
}
}
}
],
{
upsert: false,
multi: true
})
Demo # Mongo Playground
I have a document like this
{
"_id": ObjectId("626f942bb092f78afd9dad9d"),
"item_id": "external _id222",
"metadata": {
"item_name": "abc",
"quantity": 123,
"state": null
},
}
What I would like to do is, $inc i.e. increment the count of quantity and then update the state to SOLD, if quantity equals 124. I can do this by 2 queries, update quantity, do an if-else check and then update state. Is there a way to do this in one single query by update()? (preferably without aggregation)
With MongoDB v4.2+, you can do this with a single update with an aggregation pipeline to achieve atomic behaviour. Use $add to do the increment and $cond to check for quantity = 123
db.collection.update({
"item_id": "external _id222"
},
[
{
$set: {
"metadata.quantity": {
$add: [
"$metadata.quantity",
1
]
}
}
},
{
$set: {
"metadata.state": {
$cond: {
if: {
$eq: [
"$metadata.quantity",
124
]
},
then: "SOLID",
else: "$metadata.state"
}
}
}
}
],
{
multi: true
})
Here is the Mongo playground for your reference.
You can do this way
Check if quantity is 123
Then increment quantity and set state to SOLD
playground
db.collection.update({
"metadata.quantity": 123
},
{
"$inc": {
"metadata.quantity": 1
},
"$set": {
"metadata.state": "SOLD"
}
},
{
"multi": false,
"upsert": false
})
Here, the trick is that you need to check the value which is before $inc operation.
i have a question, I need to increment a field in DB by 1, thats fine i know how to do it, but i need to set max to 5, i mean it like if value in DB is 5, dont update value to 6, i have this code:
collectionName.update({
name: { $in: namesField },
}, { $inc: { fieldName: 1 } }, { multi: true });
i tried this one and many others, but does not work:
collectionName.update({
name: { $in: namesField },
}, { fieldName: { $lt: 6 }, { $inc: { fieldName: 1 } }, { multi: true });
Thank you for every idea
It is just a matter of finding only relevant documents, i.e. with fieldName smaller than 5, and then update only them, as you tried. But the finding part should be on the first argument, since the update syntax expecting query, update, options, as can be seen here
collectionName.update({
name: { $in: namesField }, fieldName: { $lt: 5 }}, { $inc: { fieldName: 1 } }, { multi: true });
I'm using the Bucket Pattern to limit documents' array size to maxBucketSize elements. Once a document's array elements is full (bucketSize = maxBucketSize), the next update will create a new document with a new array to hold more elements using upsert.
How would you copy the static fields (see below recordType and recordDesc) from the last full bucket with a single call?
Sample document:
// records collection
{
recordId: 12345, // non-unique index
recordType: "someType",
recordDesc: "Some record description.",
elements: [ { a: 1, b: 2 }, { a: 3, b: 4 } ]
bucketsize: 2,
}
Bucket implementation (copies only queried fields):
const maxBucketSize = 2;
db.collection('records').updateOne(
{
recordId: 12345,
bucketSize: { $lt: maxBucketSize } // false
},
{
$push: { elements: { a: 5, b: 6 } },
$inc: { bucketSize: 1 },
$setOnInsert: {
// Should be executed because bucketSize condition is false
// but fields `recordType` and `recordDesc` inaccessible as
// no documents matched and were not included in the query
}
},
{ upsert: true }
)
Possible solution
To make this work, I can always make two calls, findOne() to get static values and then updateOne() where I set fields with setOnInsert, but it's inefficient
How can I modify this as one call with an aggregate? Examining one (last added) document matching recordId (index), evaluate if array is full, and add new document.
Attempt:
// Evaluate last document added
db.collection('records').findOneAndUpdate(
{ recordId: 12345 },
{
$push: { elements: {
$cond: {
if: { $lt: [ '$bucketSize', maxBucketSize ] },
then: { a: 5, b: 6 }, else: null
}
}},
$inc: { bucketSize: {
$cond: {
if: { $lt: [ '$bucketSize', maxBucketSize ] },
then: 1, else: 0
}
}},
$setOnInsert: {
recordType: '$recordType',
recordDesc: '$recordDesc'
}
},
{
sort: { $natural: -1 }, // last document
upsert: true, // Bucket overflow
}
)
This comes back with:
MongoError: Cannot increment with non-numeric argument: { bucketSize: { $cond: { if: { $lt: [ "$bucketSize", 2 ] }, then: 1, else: 0 } }}
I have such a schema:
doc:
{
//Some fields
visits:
[
{
userID: Int32
time: Int64
}
]
}
I want to first check if a specific userID exists, if not, push a document with that userID and system time, else just update time value. I know neither $push nor $addToSet are not able to do that. Also using $ with upsert:true doesn't work, because of official documentation advice which says DB will use $ as field name instead of operator when trying to upsert.
Please guide me about this. Thanks
You can use $addToSet to add an item to the array and $set to update an existing item in this array.
The following will add a new item to the array if the userID is not found in the array :
db.doc.update({
visits: {
"$not": {
"$elemMatch": {
"userID": 4
}
}
}
}, {
$addToSet: {
visits: {
"userID": 4,
"time": 1482607614
}
}
}, { multi: true });
The following will update the subdocument array item if it matches the userId :
db.doc.update({ "visits.userID": 2 }, {
$set: {
"visits.$.time": 1482607614
}
}, { multi: true });
const p = await Transaction.findOneAndUpdate(
{
_id: data.id,
'products.id': { $nin: [product.id] },
},
{
$inc: {
actualCost: product.mrp,
},
$push: {
products: { id: product.id },
},
},
{ new: true }
);
or
db.collection.aggregate([
{
"$match": {
"_id": 1
}
},
{
"$match": {
"sizes.id": {
"$nin": [
7
]
}
}
},
{
"$set": {
"price": 20
}
}
])
https://mongoplayground.net/p/BguFa6E9Tra
I know it's very late. But it may help others. Starting from mongo4.4, we can use $function to use a custom function to implement our own logic. Also, we can use the bulk operation to achieve this output.
Assuming the existing data is as below
{
"_id" : ObjectId("62de4e31daa9b8acd56656ba"),
"entrance" : "Entrance1",
"visits" : [
{
"userId" : 1,
"time" : 1658736074
},
{
"userId" : 2,
"time" : 1658736671
}
]
}
Solution 1: using custom function
db.visitors.updateMany(
{_id: ObjectId('62de4e31daa9b8acd56656ba')},
[
{
$set: {
visits: {
$function: {
lang: "js",
args: ["$visits"],
body: function(visits) {
let v = []
let input = {userId: 3, time: Math.floor(Date.now() / 1000)};
if(Array.isArray(visits)) {
v = visits.filter(x => x.userId != input.userId)
}
v.push(input)
return v;
}
}
}
}
}
]
)
In NodeJS, the function body should be enclosed with ` character
...
lang: 'js',
args: ["$visits"],
body: `function(visits) {
let v = []
let input = {userId: 3, time: Math.floor(Date.now() / 1000)};
if(Array.isArray(visits)) {
v = visits.filter(x => x.userId != input.userId)
}
v.push(input)
return v;
}`
...
Solution 2: Using bulk operation:
Please note that the time here will be in the ISODate
var bulkOp = db.visitors.initializeOrderedBulkOp()
bulkOp.find({ _id: ObjectId('62de4e31daa9b8acd56656ba') }).updateOne({$pull: { visits: { userId: 2 }} });
bulkOp.find({ _id: ObjectId('62de4e31daa9b8acd56656ba') }).updateOne({$push: {visits: {userId: 2, time: new Date()}}})
bulkOp.execute()
Reference link