mongodb findOneAndUpdate only if a certain condition is met - mongodb

Following is my mongo db entries.
my-mongo-set:PRIMARY> db.stat_collection.find({name : /s/})
{ "_id" : ObjectId("5aabf231a167b3808302b138"), "name" : "shankarmr", "email" : "abc#xyz", "rating" : 9901 }
{ "_id" : ObjectId("5aabf23da167b3808302b139"), "name" : "shankar", "email" : "abc1#xyz1", "rating" : 10011 }
{ "_id" : ObjectId("5aabf2b5a167b3808302b13a"), "name" : "shankar1", "email" : "abc2#xyz2", "rating" : 10 }
{ "_id" : ObjectId("5aabf2c2a167b3808302b13b"), "name" : "shankar2", "email" : "abc3#xyz3", "rating" : 100 }
Now i want to find an entry based on name but update a field only if a certain condition holds good.
I tried the following statement, but it gives me error at the second reference to $rating.
db.stat_collection.findOneAndUpdate({name: "shankar"}, {$set : {rating : {$cond : [ {$lt : [ "$rating", 100]}, 100, $rating]}}, $setOnInsert: fullObject}, {upsert : true} )
So in my case, it shouldnot update rating for the 2nd document as the rating is not less than 100. But for the third document, rating should be updated to 100.
How do i get it work?

$max is the operator you're looking for, try:
db.stat_collection.findOneAndUpdate( { name: "shankar1"}, { $max: { rating: 100 } }, { returnNewDocument: true } )
You'll either get old value (if is greater than 100) or modify a document and set 100
According to the documentation:
The $max operator updates the value of the field to a specified value if the specified value is greater than the current value of the field. The $max operator can compare values of different types, using the BSON comparison order.

You should put all conditions in the query part of the update:
db.stat_collections.findOneAndUpdate(
{ name: "Shankar", rating: { $lt: 100 } },
$set : { rating: 100 },
);
"If the name is Shankar and rating is less than 100, then set the rating to 100." is the above.

Related

MongoDB Finding nested object value

I'm trying to find documents within my collection that have a numeric value greater than x amount. The documentation explains how to do this for top level values however I'm struggling to retrieve the correct data for values that are within child objects.
Sample JSON
{
"_id" : ObjectId("5c32646c9f3315c3e8300673"),
"key" : "20190107",
"__v" : 0,
"chart" : [
{
"_id" : ObjectId("5c3372e5c35e924984f28e03"),
"volume" : "0",
"close" : "47.24",
"time" : "09:30 AM"
},
{
"_id" : ObjectId("5c3372e5c35e924984f28d34"),
"volume" : "50",
"close" : "44.24",
"time" : "09:50 AM"
}
]
}
I want to retrieve volumes greater than 10. I've tried
db.symbols.find({"chart.volume": { $gt: 10 } } )
db.symbols.find({"volume": { $gt: 10 } } )
Any help appreciated.
Your sample JSON has string values for the chart.volume field. If it was numeric, then your first solution:
db.symbols.find({"chart.volume": { $gt: 10 } } )
would work fine. The docs do explain how to do this.

Positional operator and field limitation

In a find query projection, fields I specify after the positional operator are ignored and the whole document is always returned.
'myArray.$.myField' : 1 behave exactly like 'myArray.$' : 1
the positional operator selects the right document. But this document is quite big. I would like to project only 1 field from it.
Exemple:
db.getCollection('match').find({"participantsData.id" : 0001}, { 'participantsData.$.id': 1, })
here the response I have
{
"_id" : "myid",
"matchCreation" : 1463916465614,
"participantsData" : [
{
"id" : 0001,
"plenty" : "of",
"other" : "fields",
"and" : "subdocuments..."
}
]
}
This is what I want
{
"_id" : "myid",
"matchCreation" : 1463916465614,
"participantsData" : [
{
"id" : 0001
}
]
}
Is it possible with mongo?
Yes it can be done in mongo
Please try the below query
db.getCollection('match').find(
{"participantsData.id" : 0001},
{"participantsData.id": 1, "matchCreation": 1 })
This will give you the below result
{
"_id" : "myid",
"matchCreation" : 1463916465614,
"participantsData" : [
{
"id" : 1
}
]
}

removing an object from a mongodb document

I have a document in Mongodb collection, where I want to remove an object, using title key.
I tried using $unset, but it only removes the title key not the object to which it belongs.
{
"_id" : ObjectId("576b63d49d20504c1360f688"),
"books" : [
{
"art_id" : ObjectId("574e68e5ac9fbac82489b689"),
"title" :"abc",
"price" : 40
},
{
"art_id" : ObjectId("575f9badada0500d192c53f4"),
"title" : "xyz",
"price" : 20
},
{
"art_id" : ObjectId("57458224d86b3d1561150f17"),
"title" : "def",,
"price" : 30
}
],
"user_id" : "575570c315e27d13167dfc0d"
}
To remove the entire object that contains the query object use db.remove() query.
For your case:
db.yourcollection.remove({"books.title": "abc"});
Please double check the format in which the element of array is referenced.
This removes the entire objects that contains the embedded query obj. To remove only a single object, provide it with another field to uniquely identify it.
If you only want to remove the object that contains the title field from the array but wants to keep the object that contains the array, then please use the $pull operator. This answer will be of help.
Example: if you want to remove object
{
"art_id" : ObjectId("574e68e5ac9fbac82489b689"),
"title" :"abc",
"price" : 40
}
just from the array but keep the parent object like
{
"_id" : ObjectId("576b63d49d20504c1360f688"),
"books" : [
{
"art_id" : ObjectId("575f9badada0500d192c53f4"),
"title" : "xyz",
"price" : 20
},
{
"art_id" : ObjectId("57458224d86b3d1561150f17"),
"title" : "def",,
"price" : 30
}
],
"user_id" : "575570c315e27d13167dfc0d"
}
use
db.mycollection.update(
{'_id': ObjectId("576b63d49d20504c1360f688")},
{ $pull: { "books" : { "title": "abc" } } },
false,
true
);
$unset won't remove the object from an array. The $unset operator deletes a particular field. doc.
Use $pull instead.
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
Try following query
db.collName.update({$pull : {books:{title:abc}}})
Refer $pull-doc
Hope this will help you.
And if... ¿Do I want to delete an object that is inside a document and not as an array?
{
"_id" : ObjectId("576b63d49d20504c1360f688"),
"books" : {
"574e68e5ac9fbac82489b689": {
"art_id" : ObjectId("574e68e5ac9fbac82489b689"),
"title" :"abc",
"price" : 40
},
"575f9badada0500d192c53f4": {
"art_id" : ObjectId("575f9badada0500d192c53f4"),
"title" :"xyz",
"price" : 20
},
"57458224d86b3d1561150f17": {
"art_id" : ObjectId("57458224d86b3d1561150f17"),
"title" : "def",
"price" : 30
}
},
"user_id" : "575570c315e27d13167dfc0d"
}
The solutions is this:
db.auctions.update(
{'_id': ObjectId("576b63d49d20504c1360f688")},
{$unset: {"books.574e68e5ac9fbac82489b689":
{_id: "574e68e5ac9fbac82489b689"}}})
Try using pull.
https://docs.mongodb.com/manual/reference/operator/update/pull/
$pull
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
The $pull operator has the form:
{ $pull: { <field1>: <value|condition>, <field2>: <value|condition>, ... } }
To specify a <field> in an embedded document or in an array, use dot notation.
Try in the mongo shell
db.yourcollection.remove({books:[{title:'title_you_want'}]})
Careful with the braces.

How can I select a number of records per a specific field using mongodb?

I have a collection of documents in mongodb, each of which have a "group" field that refers to a group that owns the document. The documents look like this:
{
group: <objectID>
name: <string>
contents: <string>
date: <Date>
}
I'd like to construct a query which returns the most recent N documents for each group. For example, suppose there are 5 groups, each of which have 20 documents. I want to write a query which will return the top 3 for each group, which would return 15 documents, 3 from each group. Each group gets 3, even if another group has a 4th that's more recent.
In the SQL world, I believe this type of query is done with "partition by" and a counter. Is there such a thing in mongodb, short of doing N+1 separate queries for N groups?
You cannot do this using the aggregation framework yet - you can get the $max or top date value for each group but aggregation framework does not yet have a way to accumulate top N plus there is no way to push the entire document into the result set (only individual fields).
So you have to fall back on MapReduce. Here is something that would work, but I'm sure there are many variants (all require somehow sorting an array of objects based on a specific attribute, I borrowed my solution from one of the answers in this question.
Map function - outputs group name as a key and the entire rest of the document as the value - but it outputs it as a document containing an array because we will try to accumulate an array of results per group:
map = function () {
emit(this.name, {a:[this]});
}
The reduce function will accumulate all the documents belonging to the same group into one array (via concat). Note that if you optimize reduce to keep only the top five array elements by checking date then you won't need the finalize function, and you will use less memory during running mapreduce (it will also be faster).
reduce = function (key, values) {
result={a:[]};
values.forEach( function(v) {
result.a = v.a.concat(result.a);
} );
return result;
}
Since I'm keeping all values for each key, I need a finalize function to pull out only latest five elements per key.
final = function (key, value) {
Array.prototype.sortByProp = function(p){
return this.sort(function(a,b){
return (a[p] < b[p]) ? 1 : (a[p] > b[p]) ? -1 : 0;
});
}
value.a.sortByProp('date');
return value.a.slice(0,5);
}
Using a template document similar to one you provided, you run this by calling mapReduce command:
> db.top5.mapReduce(map, reduce, {finalize:final, out:{inline:1}})
{
"results" : [
{
"_id" : "group1",
"value" : [
{
"_id" : ObjectId("516f011fbfd3e39f184cfe13"),
"name" : "group1",
"date" : ISODate("2013-04-17T20:07:59.498Z"),
"contents" : 0.23778377776034176
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe0e"),
"name" : "group1",
"date" : ISODate("2013-04-17T20:07:59.467Z"),
"contents" : 0.4434165076818317
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe09"),
"name" : "group1",
"date" : ISODate("2013-04-17T20:07:59.436Z"),
"contents" : 0.5935856597498059
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe04"),
"name" : "group1",
"date" : ISODate("2013-04-17T20:07:59.405Z"),
"contents" : 0.3912118375301361
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfdff"),
"name" : "group1",
"date" : ISODate("2013-04-17T20:07:59.372Z"),
"contents" : 0.221651989268139
}
]
},
{
"_id" : "group2",
"value" : [
{
"_id" : ObjectId("516f011fbfd3e39f184cfe14"),
"name" : "group2",
"date" : ISODate("2013-04-17T20:07:59.504Z"),
"contents" : 0.019611883210018277
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe0f"),
"name" : "group2",
"date" : ISODate("2013-04-17T20:07:59.473Z"),
"contents" : 0.5670706110540777
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe0a"),
"name" : "group2",
"date" : ISODate("2013-04-17T20:07:59.442Z"),
"contents" : 0.893193120136857
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe05"),
"name" : "group2",
"date" : ISODate("2013-04-17T20:07:59.411Z"),
"contents" : 0.9496864483226091
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe00"),
"name" : "group2",
"date" : ISODate("2013-04-17T20:07:59.378Z"),
"contents" : 0.013748752186074853
}
]
},
{
"_id" : "group3",
...
}
]
}
],
"timeMillis" : 15,
"counts" : {
"input" : 80,
"emit" : 80,
"reduce" : 5,
"output" : 5
},
"ok" : 1,
}
Each result has _id as group name and values as array of most recent five documents from the collection for that group name.
you need aggregation framework $group stage piped in a $limit stage...
you want also to $sort the records in some ways or else the limit will have undefined behaviour, the returned documents will be pseudo-random (the order used internally by mongo)
something like that:
db.collection.aggregate([{$group:...},{$sort:...},{$limit:...}])
here there is the documentation if you want to know more

MongoDb - How to search BSON composite key exactly?

I have a collection that stored information about devices like the following:
/* 1 */
{
"_id" : {
"startDate" : "2012-12-20",
"endDate" : "2012-12-30",
"dimensions" : ["manufacturer", "model"],
"metrics" : ["deviceCount"]
},
"data" : {
"results" : "1"
}
}
/* 2 */
{
"_id" : {
"startDate" : "2012-12-20",
"endDate" : "2012-12-30",
"dimensions" : ["manufacturer", "model"],
"metrics" : ["deviceCount", "noOfUsers"]
},
"data" : {
"results" : "2"
}
}
/* 3 */
{
"_id" : {
"dimensions" : ["manufacturer", "model"],
"metrics" : ["deviceCount", "noOfUsers"]
},
"data" : {
"results" : "3"
}
}
And I am trying to query the documents using the _id field which will be unique. The problem I am having is that when I query for all the different attributes as in:
db.collection.find({$and: [{"_id.dimensions":{ $all: ["manufacturer","model"], $size: 2}}, {"_id.metrics": { $all:["noOfUsers","deviceCount"], $size: 2}}]});
This matches 2 and 3 documents (I don't care about the order of the attributes values), but I would like to only get 3 back. How can I say that there should not be any other attributes to _id than those that I specify in the search query?
Please advise. Thanks.
Unfortunately, I think the closest you can get to narrowing your query results to just unordered _id.dimensions and unordered _id.metrics requires you to know the other possible fields in the _id subdocument field, eg. startDate and endDate.
db.collection.find({$and: [
{"_id.dimensions":{ $all: ["manufacturer","model"], $size: 2}},
{"_id.metrics": { $all:["noOfUsers","deviceCount"], $size: 2}},
{"_id.startDate":{$exists:false}},
{"_id.endDate":{$exists:false}}
]});
If you don't know the set of possible fields in _id, then the other possible solution would be to specify the exact _id that you want, eg.
db.collection.find({"_id" : {
"dimensions" : ["manufacturer", "model"],
"metrics" : ["deviceCount", "noOfUsers"]
}})
but this means that the order of _id.dimensions and _id.metrics is significant. This last query does a document match on exact BSON representation of _id.