$mul nested document field where might not exist - mongodb

I have the following document:
{
"results" : [
{
"name" : "foo",
"score" : 10
},
{
"name" : "bar"
}
]
}
I want to multiply the score field by 10, only where it exists.
Using just dot notation:
{
$mul: {
'results.score': NumberInt(10)
}
}
Returns an error:
Cannot create field 'score' in element {results: [ { name: "foo", score: 10 }, { name: "bar" } ]}
I've tried using the new array filters:
{
$mul: {
'results.$[result].score': NumberInt(10)
}
}, {
arrayFilters: [{
'result.grade': {
$exists: true
}
}]
}
This gives me an error too:
No array filter found for identifier 'result' in path 'results.$[result].score'
I know that I could set a score field in all the documents and set it to zero, that wouldn't be a solution though as the lack of a score means that there isn't a score yet, rather that there is a score and it's 0.
Can this be done?
Could this be done prior to version 3.6?

Related

Overwrite value and create key while update query in mongodb

I have a mongodb collection that looks like this:
{
"_id" : ObjectId("60471bd482c0da3c0e70d26f"),
"owner" : "John",
"propAvailable" : {
"val1" : true
}
},
{
"_id" : ObjectId("60471bd482c0da3c0e76523f"),
"owner" : "Matt",
"propAvailable" : {
"val1" : {
"val2" : true
}
}
I need to run an update query on this collection that will update the value of the 'propAvailable' key such that
db.collection('props').update({'owner' : 'John'} , {$set : {'propAvailable.val1.val2' : true}});
This query works if the document already looks like the second one but gives the error:
Cannot create field 'val2' in element {'val1': true} if the document format is the first one. Is there a way to write this query so that it overwrites the boolean 'true' and replaces it with the object {'val2' : true}
You can use:
db.collection.update({
"owner": "John"
},
{
$set: {
"propAvailable.val1": {
val2: true
}
}
})
To create val2: true inside propAvailable.val1 and replace its current content.
As you can see working on the playground
If you're using Mongo version 4.2+ you can use pipelined updates to achieve this, like so:
db.collection.updateMany({
owner: "John"
},
[
{
$set: {
"propAvailable.val1": {
$mergeObjects: [
{
$cond: [
{
$eq: [
"object",
{
$type: "$propAvailable.val1"
}
]
},
"$propAvailable.val1",
{}
]
},
{
val2: true
}
]
}
}
},
])
Mongo Playground
For older mongo versions this is impossible to do in 1 query if objects potentially have additional fields under val1 you want to preserve. You will have to either read and update, or execute two different updates for each case.

Append an object to an array inside a nested object

I have a collection in MongoDB in which one of the documents looks like this:
{
_id: ObjectId("6162883719592ea3350d3c87"),
fullName: 'Random User',
username: 'ruser1',
password: 'asdadasd',
portfolio: [ { equity: [] }, { crypto: [] }, { etf: [] }, { cash: [] } ]
}
I am trying to append a new object of the following format to the equity array inside the portfolio.
Object format:
{
name : "AAPL",
quantity : 1,
price : 100
}
I was trying to use the $push to do this operation, but I'm encountering the following error:
db.users.updateOne(
{_id : ObjectId("6162883719592ea3350d3c87")},
{$push : {"portfolio.equity" : {
name : "AAPL",
quantity : 1,
price : 100
}
}
}
)
MongoServerError: Cannot create field 'equity' in element {portfolio: [ { equity: [] }, { crypto: [] }, { etf: [] }, { cash: [] } ]}
I have also tried to use portfolio.$.equity, but that did not work either.
db.users.updateOne(
{_id : ObjectId("6162883719592ea3350d3c87")} ,
{$push : {"portfolio.$.equity" : {name : "AAPL", price : 100, quantity : 1}}}
)
MongoServerError: The positional operator did not find the match needed from the query.
In short, I am trying to append an object to an array inside an object's object.
How can I resolve this error or what is the appropriate way to do this?
You can use arrayFilters with check portfolio.equity field is existed via $exists.
db.users.updateOne({
_id: ObjectId("6162883719592ea3350d3c87")
},
{
$push: {
"portfolio.$[portfolio].equity": {
name: "AAPL",
price: 100,
quantity: 1
}
}
},
{
arrayFilters: [
{
"portfolio.equity": {
$exists: true
}
}
]
})
Sample Mongo Playground

What is the right prototype of using $mul in such requirement?

I am new at MongoDB and I want to increase the "rate2" of the "increase" array by 100%. It is located in my "CUSTOMER.ratings" document that has first name of "Jimmy".
The collection is like this:
db.collection.insert(
{
"CUSTOMER": {
"first name": "Jimmy",
"ratings": [{"increase": {"rate":99}, {"increase": {"rate2": 20.5}}]
}
}
);
I tried the following, but it created a new set insted putting a new address inside the array of location:
db.collection.update({"CUSTOMER.first name": "Jimmy"}, {$mul:{"ratings":{"increase":{ "rate2": 2 }}}});
I tried to put the "ratings" prior to the $mul put it doesn't work.
I need the right prototype of doing such thing.
The expected outcome is the following:
db.collection.insert(
{
"CUSTOMER": {
"first name": "Jimmy",
"ratings": [{{"increase": "rate": 99}},
{"increase": "rate": 41}}
]
}
}
);
To sum up, I want to increase rate2 by 100% .
To not be confusing, consider the following:
A
B[
C {rate}
C {rate2} ]
So how to multiply the value of "rate2"?
I tried {$mul: {"A":{"B":{"C":2 }}}};
Also, I tried {$mul: {"A.B.C": 2}};
Also, I tried {"A.B": {$mul: {"C": 2}}};
Also, I have used the $pull and $push prototype, but not sure why not working!!
Consider the input document:
{
"_id" : 1,
"CUSTOMER" : {
"first name" : "Jimmy",
"ratings" : [
{
"increase" : {
"rate" : 99
}
},
{
"increase" : {
"rate2" : 20.5
}
}
]
}
}
The following query will update the document and multiply (by 2 and update) the nested field rate2 ("CUSTOMER.ratings.increase.rate2").
db.collection.updateOne(
{ _id: 1 },
{ $mul: { "CUSTOMER.ratings.$[e].increase.rate2": 2 } },
{ arrayFilters: [ { "e.increase.rate2": { $exists: true } } ] }
)
Note the usage of the arrayFilters and the filtered positional operator $[some_id] (which is used to update specific array elements as mentioned in the arrayFilters condition).
[EDIT ADD]
Another way of updating: Updating using the positional $ update operator:
db.collection.updateOne(
{ "CUSTOMER.ratings.increase.rate2": { $exists: true } },
{ $mul: { "CUSTOMER.ratings.$.increase.rate2": 2 } }
)

Mongodb: find documents with array field that contains more than one SAME specified value

There is three documents in collection test:
// document 1
{
"id": 1,
"score": [3,2,5,4,5]
}
// document 2
{
"id": 2,
"score": [5,5]
}
// document 3
{
"id": 3,
"score": [5,3,3]
}
I want to fetch documents that score field contains [5,5].
query:
db.test.find( {"score": {"$all": [5,5]}} )
will return document 1, 2 and 3, but I only want to fetch document 1 and 2.
How can I do this?
After reading your problem I personally think mongodb not supported yet this kind of query. If any one knows about how to find this using mongo query they defiantly post answers here.
But I think this will possible using mongo forEach method, so below code will match your criteria
db.collectionName.find().forEach(function(myDoc) {
var scoreCounts = {};
var arr = myDoc.score;
for (var i = 0; i < arr.length; i++) {
var num = arr[i];
scoreCounts[num] = scoreCounts[num] ? scoreCounts[num] + 1 : 1;
}
if (scoreCounts[5] >= 2) { //scoreCounts[5] this find occurrence of 5
printjsononeline(myDoc);
}
});
Changed in version 2.6.
The $all is equivalent to an $and operation of the specified values; i.e. the following statement:
{ tags: { $all: [ "ssl" , "security" ] } }
is equivalent to:
{ $and: [ { tags: "ssl" }, { tags: "security" } ] }
I think you need to pass in a nested array -
So try
db.test.find( {"score": {"$all": [[5,5]]}} )
Source
Changed in version 2.6.
When passed an array of a nested array (e.g. [ [ "A" ] ] ), $all can now match documents where the field contains the nested array as an element (e.g. field: [ [ "A" ], ... ]), or the field equals the nested array (e.g. field: [ "A" ]).
http://docs.mongodb.org/manual/reference/operator/query/all/
You can do it with an aggregation. The first step can use an index on { "score" : 1 } but the rest is hard work.
db.test.aggregate([
{ "$match" : { "score" : 5 } },
{ "$unwind" : "$score" },
{ "$match" : { "score" : 5 } },
{ "$group" : { "_id" : "$_id", "sz" : { "$sum" : 1 } } }, // use $first here to include other fields in the results
{ "$match" : { "sz" : { "$gte" : 2 } } }
])

way to update multiple documents with different values

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.