Yet another MongoDB findAndModify - mongodb

Please consider the following document, a part of the Runtime collection:
{
"entity_id" : 10,
"features" : [
{
"10" : "Test System 2"
},
{
"20" : "System 2 Description"
},
{
"180" : ISODate("2013-12-25T18:19:40.589Z")
},
{
"190" : ISODate("2013-12-25T18:19:40.589Z")
}
],
"_id" : ObjectId("52bb21bc8a2ebdc01c000001")
}
My goal is to update the value of the element of the "features" array having the key "20".
Here are the things I've tried (in mongo shell):
db.Runtime.findAndModify({ "query" : {"_id": "52bb21bc8a2ebdc01c000001"}, "update" : {$set : {"features.$.20":"Updated Description"}}} );
db.Runtime.findAndModify({ "query" : {"_id": "52bb21bc8a2ebdc01c000001"}, "update" : {$set : {"features['20']":"Updated Description"}}} );
db.Runtime.findAndModify({ "query" : {"_id": "52bb21bc8a2ebdc01c000001"}, "update" : {$set : {"features[1]":"Updated Description"}}} );
In all instances the shell prints
null
and nothing happens to the data. So, the main question is, of course, what is wrong with my code snippets. Also, how that "null" is supposed to get interpreted? And is there such a thing as mongo shell's log where one could find any clues? Many thanks for your help!

When using the $ positional operator in an update, your query needs to include a term that matches the element of the array you're updating:
db.Runtime.findAndModify({
query: {_id: ObjectId("52bb21bc8a2ebdc01c000001"),
'features.20': {$exists: true}},
update: {$set: {"features.$.20":"Updated Description"}}
})
Note that you also need to call ObjectId on your _id string to turn it into an actual ObjectId or it won't match the doc.

Related

How to find mongoDB object by ObjectID w/MongoDB Shell?

How can I do this in the MongoDB shell?
https://stackoverflow.com/a/14315888/8887398
Basically I'm trying to find objects by their ObjectId. All are failing so I assume it's trying to compare to string. I'm doing this directly in the MongoDB shell. Here's what I've tried:
db.myTestDB.find( {_id: 3254ummx213u5k815mn2v2 })
Result: SyntaxError: identifier starts immediately after numeric literal
db.myTestDB.find( {_id: "3254ummx213u5k815mn2v2" })
Result: Nothing
db.myTestDB.find( { "_id": "3254ummx213u5k815mn2v2" })
Result: Nothing
db.myTestDB.find( {_id: ObjectId("3254ummx213u5k815mn2v2") })
Result: Nothing
I just put a random ID in there for my examples, but I've verified the ID exists, and by looking on MongoDB Compass.
From the docs, you construct an ObjectId by using the new keyword. For example:
db.myTestDB.find( {_id: new ObjectId("3254ummx213u5k815mn2v2") })
So, you were on the right track with the last attempt in your question, you were only missing the new keyword.
Here's a more complete example. Given the following docs:
db.getCollection('X').find({})
{
"_id" : ObjectId("59d0ada3c26584cd8b79fc51"),
"name" : "A"
}
{
"_id" : ObjectId("59d0adafc26584cd8b79fc54"),
"name" : "B"
}
{
"_id" : ObjectId("59d0b2b4c26584cd8b79fd7c"),
"name" : "C"
}
This shell command will load one of those docs, by id:
db.getCollection('X').find({_id: new ObjectId("59d0ada3c26584cd8b79fc51")})
{
"_id" : ObjectId("59d0ada3c26584cd8b79fc51"),
"name" : "A"
}

incrementing version number in a subdocument and the parent document

I am trying to add versioning levels in a document as well as its subdocuments. Heres a schema example
{
"_id" : ObjectId("59d1312a8ee6de1858933950"),
"synonyms" : [
{
"_id" : ObjectId("59d1312a8ee6de1858933954"),
"text" : [
1.0,
2.0,
3.0
],
"__v" : 1.0
},
{
"_id" : ObjectId("59d1312a8ee6de1858933953"),
"text" : [
"Foo ",
"bar ",
"Baz"
],
"__v" : 0
},
{
"_id" : ObjectId("59d1312a8ee6de1858933951"),
"text" : [
"fizz",
"bazz",
"bizz"
],
"__v" : 0
}
],
"__v" : 3.0
}
As you can see , the parent document has its own __v while each subdocument (part of the synonyms array) also has its __v . What i'm trying to accomplish is this
When updating a subdocument array - increment the version of the subdocument as well as its parent version
to that effect , i've tried the below code
db.collection.update({
'_id': ObjectId("59d1312a8ee6de1858933950"),
"synonyms._id": ObjectId("59d1312a8ee6de1858933954")
},
{$set: {'synonyms.$.text': [1,2,3]}, $inc: {'synonyms.$.__v': 1}, $inc: {"__v": 1}}
)
My parent __v is getting increment on every update but the subdocument seems to be stuck at 1.0 no matter how many updates i go through. Is there a better way?
If you think about the parameters that you pass to a MongoDB command as a JSON document rather than a string this makes perfect sense: Passing in the same operator twice (as in $inc at the start and later another $inc again in your example) will create a JSON document that only contains the last parameter. This would be different if you were using a string here which would actually represent a JSON document with two $inc fields.
So here's how to get it right (basically by $incing two fields as part of a single operation):
db.collection.update({
'_id': ObjectId("59d1312a8ee6de1858933950"),
"synonyms._id": ObjectId("59d1312a8ee6de1858933954")
},
{$set: {'synonyms.$.text': [1,2,3]}, $inc: {'synonyms.$.__v': 1, "__v": 1}}
)

MongoDB querying to with changing values for key

Im trying to get back into Mongodb and Ive come across something that I cant figure out.
I have this data structure
> db.ratings.find().pretty()
{
"_id" : ObjectId("55881e43424cbb1817137b33"),
"e_id" : ObjectId("5565e106cd7a763b2732ad7c"),
"type" : "like",
"time" : 1434984003156,
"u_id" : ObjectId("55817c072e48b4b60cf366a7")
}
{
"_id" : ObjectId("55893be1e6a796c0198e65d3"),
"e_id" : ObjectId("5565e106cd7a763b2732ad7c"),
"type" : "dislike",
"time" : 1435057121808,
"u_id" : ObjectId("55817c072e48b4b60cf366a7")
}
{
"_id" : ObjectId("55893c21e6a796c0198e65d4"),
"e_id" : ObjectId("5565e106cd7a763b2732ad7c"),
"type" : "null",
"time" : 1435057185089,
"u_id" : ObjectId("55817c072e48b4b60cf366a7")
}
What I want to be able to do is count the documents that have either a like or dislike leaving the "null" out of the count. So I should have a count of 2. I tried to go about it like this whereby I set the query to both fields:
db.ratings.find({e_id: ObjectId("5565e106cd7a763b2732ad7c")}, {type: "like", type: "dislike"})
But this just prints out all three documents. Is there any reason?
If its glaringly obvious im sorry pulling out my hair at the moment.
Use the following db.collection.count() method which returns the count of documents that would match a find() query:
db.ratings.count({
"e_id": ObjectId("5565e106cd7a763b2732ad7c"),
type: {
"$in": ["like", "dislike"]
}
})
The db.collection.count() method is equivalent to the db.collection.find(query).count() construct. Your query selection criteria above can be interpreted as:
Get me the count of all documents which have the e_id field values as ObjectId("5565e106cd7a763b2732ad7c") AND the type field which has either value "like" or "dislike", as depicted by the $in operator that selects the documents where the value of a field equals any value in the specified array.
db.ratings.find({e_id: ObjectId("5565e106cd7a763b2732ad7c")},
{type: "like", type: "dislike"})
But this just prints out all three
documents. Is there any reason? If its glaringly obvious im sorry
pulling out my hair at the moment.
The second argument here is the projection used by the find method . It specifies fields that should be included -- regardless of their value. Normally, you specify a boolean value of 1 or true to include the field. Obviously, MongoDB accepts other values as true.
If you only need to count documents, you should issue a count command:
> db.runCommand({count: 'collection',
query: { "e_id" : ObjectId("5565e106cd7a763b2732ad7c"),
type: { $in: ["like", "dislike"]}}
})
{ "n" : 2, "ok" : 1 }
Please note the Mongo Shell provides the count helper for that:
> db.collection.find({ "e_id" : ObjectId("5565e106cd7a763b2732ad7c"),
type: { $in: ["like", "dislike"]}}).count()
2
That being said, to quote the documentation, using the count command "can result in an inaccurate count if orphaned documents exist or if a chunk migration is in progress." To avoid that, you might prefer using the aggregation framework:
> db.collection.aggregate([
{ $match: { "e_id" : ObjectId("5565e106cd7a763b2732ad7c"),
type: { $in: ["like", "dislike"]}}},
{ $group: { _id: null, n: { $sum: 1 }}}
])
{ "_id" : null, "n" : 2 }
This query should solve your problem
db.ratings.find({$or : [{"type": "like"}, {"type": "dislike"}]}).count()

Mongodb Update/Upsert array exact match

I have a collection :
gStats : {
"_id" : "id1",
"criteria" : ["key1":"value1", "key2":"value2"],
"groups" : [
{"id":"XXXX", "visited":100, "liked":200},
{"id":"YYYY", "visited":30, "liked":400}
]
}
I want to be able to update a document of the stats Array of a given array of criteria (exact match).
I try to do this on 2 steps :
Pull the stat document from the array of a given "id" :
db.gStats.update({
"criteria" : {$size : 2},
"criteria" : {$all : [{"key1" : "2096955"},{"value1" : "2015610"}]}
},
{
$pull : {groups : {"id" : "XXXX"}}
}
)
Push the new document
db.gStats.findAndModify({
query : {
"criteria" : {$size : 2},
"criteria" : {$all : [{"key1" : "2015610"}, {"key2" : "2096955"}]}
},
update : {
$push : {groups : {"id" : "XXXX", "visited" : 29, "liked" : 144}}
},
upsert : true
})
The Pull query works perfect.
The Push query gives an error :
2014-12-13T15:12:58.571+0100 findAndModifyFailed failed: {
"value" : null,
"errmsg" : "exception: Cannot create base during insert of update. Cause
d by :ConflictingUpdateOperators Cannot update 'criteria' and 'criteria' at the
same time",
"code" : 12,
"ok" : 0
} at src/mongo/shell/collection.js:614
Neither query is working in reality. You cannot use a key name like "criteria" more than once unless under an operator such and $and. You are also specifying different fields (i.e groups) and querying elements that do not exist in your sample document.
So hard to tell what you really want to do here. But the error is essentially caused by the first issue I mentioned, with a little something extra. So really your { "$size": 2 } condition is being ignored and only the second condition is applied.
A valid query form should look like this:
query: {
"$and": [
{ "criteria" : { "$size" : 2 } },
{ "criteria" : { "$all": [{ "key1": "2015610" }, { "key2": "2096955" }] } }
]
}
As each set of conditions is specified within the array provided by $and the document structure of the query is valid and does not have a hash-key name overwriting the other. That's the proper way to write your two conditions, but there is a trick to making this work where the "upsert" is failing due to those conditions not matching a document. We need to overwrite what is happening when it tries to apply the $all arguments on creation:
update: {
"$setOnInsert": {
"criteria" : [{ "key1": "2015610" }, { "key2": "2096955" }]
},
"$push": { "stats": { "id": "XXXX", "visited": 29, "liked": 144 } }
}
That uses $setOnInsert so that when the "upsert" is applied and a new document created the conditions specified here rather than using the field values set in the query portion of the statement are used instead.
Of course, if what you are really looking for is truly an exact match of the content in the array, then just use that for the query instead:
query: {
"criteria" : [{ "key1": "2015610" }, { "key2": "2096955" }]
}
Then MongoDB will be happy to apply those values when a new document is created and does not get confused on how to interpret the $all expression.

MongoDB update a subdocument attribute

Let me start by saying I'm sorry if this has been answered, but I can't get other questions on this site to fit my needs and, more importantly, work.
I have the below example document, with a subdocument of 'address':
{
"_id" : ObjectId("....")
,"addresses" :
[{
"start" : ISODate("1973-07-10T00:11:51.111Z")
,"value" : "123 long road"
}]
}
What I need to do is to close the existing address record with an end attribute, and add a new line for the new address with a new start and value attribute. Eventually, I'll need to do this again so the code needs to update the subdocument record where end does not exist.
The below code does not work, but it's about as far as I can get:
db.sites.update(
{"_id" : ObjectId("....")
, "addresses.end" : {"$exists" : false}}
,{"$set": {"addresses.$.end" : "fdsa"}});
This gives the error:
Cannot apply the positional operator without a corresponding query field containing an array.
Can anyone point me in the right direction?
Juste replace in your query "addresses.end" : {"$exists" : false} with:
addresses: {$elemMatch: {end: {$exists: false}}}
Your address field is poorly defined. you need make it a subdocument or an array of subdocuments. ie {
"_id" : ObjectId("....")
,"addresses" :
[
{
"start" : ISODate("1973-07-10T00:11:51.111Z")
,"value" : "123 long road"
}
]
}
your query should then work!
I think that the Query should be more specific
**Updated **
db.sites.update ( {"_id" : ObjectId("...."), addresses: { "$elemMatch" : { end:{$exists : false}} } }, {"$set": {"addresses.$.end" : "fdsa"}});
db.sites.find()
results:
{
"_id" : ObjectId("53df93da560b7815e1237934"),
"addresses" : [
{
"start" : ISODate("1973-07-10T00:11:51.111Z"),
"value" : "123 long road",
"end" : "fdsa"
}
]
}
but you can update only one
Take a look http://docs.mongodb.org/manual/reference/operator/projection/positional/#proj.S
You can t update more element in Array https://jira.mongodb.org/browse/SERVER-1243