How can I update a field starting with dollar in an embedded document in MongoDB? - mongodb

I have a document in MongoDB in a collection with a document which has a field x which value is an embedded document, created this way:
> db.c.insert({_id: 1, x: {$a: 2, b: 3}})
WriteResult({ "nInserted" : 1 })
> db.c.findOne({_id: 1})
{ "_id" : 1, "x" : { "$a" : 2, "b" : 3 } }
Note there is a field in the embedded document which starts with dollar ($a) and a field that doesn't start with dollar (b). I can update the field without dolar without problem:
> db.c.updateOne({_id: 1}, {$set: {"x.b": 30}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.c.findOne({_id: 1})
{ "_id" : 1, "x" : { "$a" : 2, "b" : 30 } }
However, if I try to update the field which starts with dollar in the same way, I get an error
> db.c.updateOne({_id: 1}, {$set: {"x.$a": 20}})
WriteError({
"index" : 0,
"code" : 52,
"errmsg" : "The dollar ($) prefixed field '$a' in 'x.$a' is not valid for storage.",
"op" : {
"q" : {
"_id" : 1
},
"u" : {
"$set" : {
"x.$a" : 20
}
},
"multi" : false,
"upsert" : false
}
})
Thus, how can I update a field starting with dollar in an embedded document?
I'm using MongoDB 4.4.1, in the case you need to know.
Thanks!

It is not possible to update field that start with $, see mongodb field name instructions,
Restrictions on Field Names:
Field names cannot contain the null character.
Top-level field names cannot start with the dollar sign ($) character.
Otherwise, starting in MongoDB 3.6, the server permits storage of field names that contain dots (i.e. .) and dollar signs (i.e. $).
IMPORTANT
The MongoDB Query Language cannot always meaningfully express queries over documents whose field names contain these characters (see SERVER-30575).
Until support is added in the query language, the use of $ and . in field names is not recommended and is not supported by the official MongoDB drivers.

Related

MongoDB cannot sort the _id?

I have a simple order table in mongoDB
{
"_id" : NumberInt(2),
"bar" : "Maggie Choos Bar"
},
{
"_id" : NumberInt(3),
"bar" : "Corona Bar"
{
I want to find the BIGGEST "_id" number in the table
db.getCollection("order").find({}).sort({"$_id":-1}).limit(1);
But no matter if I sort 1 or -1 I keep getting the result with _id : 2
Any ideas?
The field name prefixed with a dollar sign, like $_id, is for references in an aggregation pipeline. For a sort document, use just the field name:
db.getCollection("order").find({}).sort({"_id":-1}).limit(1);

Meteor/Mongo nested arrays update

I'm new to meteor/mongo/js as a stack and I'm getting lost in JSON arrays and referencing them. Based another SO answer ( and the docs) I think I am close...
A document in the Orders collection, the document has nested arrays.
Order -> orderLines -> lineItems:
Sample doc:
{
"_id" : "27tGpRtMWYqPpFkDN",
"orderLines" : [
{
"lineId" : 1,
"name" : "Cheese & Pickle",
"instructions" : "",
"lineItems" : [
{
"name" : "Cheddar Cheese",
"quantity" : 1
},
{
"name" : "Branston Pickle",
"quantity" : 1
},
{
"name" : "Focaccia Roll",
"quantity" : 1
}
]
}
]
}
What I'm trying to do from the meteor/mongo shell:
Add "instructions" of "foo" to orderLines where lineId = 1
Put a new item on lineItems array
This appears to hang...
meteor:PRIMARY> db.orders.update({_id:"27tGpRtMWYqPpFkDN","orderLines.lineId":"1", {$set: {"orderLines.$.instructions":"foo"}})
...
This doesn't like the identifier in the query
meteor:PRIMARY> db.orders.update({_id:"27tGpRtMWYqPpFkDN", "orderLines.lineId":"1"}, {$push:{"orderLines.$.lineItems":" { "name" : "butter", "quantity" : 1}"}});
2015-10-27T16:09:54.489+0100 SyntaxError: Unexpected identifier
Thanks all for your comments... but I found out some answers, posted for reference
Item 1 - using $set on a value within an array
This was failing due to two typos, one missing closing } at the end of the query, second was quoting the value "1" for itemId in the query.
This works:
db.orders.update({_id:"27tGpRtMWYqPpFkDN", orderLines.lineId":1},
{$set: {"orderLines.$.instructions":"foo"}})
I also realised that when I said "It appears to hang" it is the cli waiting for a valid statement, so hints at a missing } or )!
Item 2 - using $push to add data to an array - 2 levels nested
This was failing due to quoting around the array data
db.orders.update({_id:"27tGpRtMWYqPpFkDN", "orderLines.lineId":1 },
{$push:{"orderLines.$.lineItems": { "name" : "butter", "quantity" : 1} }})
Nested Arrays: can use $ positional operator
What I want to do next is use $set on an item in the second level array, and this would require using the $ positional operator twice:
db.orders.update({"orderLines.lineId":1, lineItems.name:"Cheddar Cheese"},
{$set: {"orderLines.$.lineItems.$.quantity": 2}})
This throws an error:
Too many positional (i.e. '$') elements found in path
There is an open MongoDB enhancement request for this but it's been open since 2010
$push adds a new element to an array. You're merely trying to set the value of a particular key in an array element.
Try:
db.orders.update({ _id: "27tGpRtMWYqPpFkDN", "orderLines.lineId": 1},
{ $set: { "orderlines.$.instructions": "foo" }})
docs

mongodb $elemMatch in query return all sub docs

db.aaa.insert({"_id":1, "m":[{"_id":1,"a":1},{"_id":2,"a":2}]})
db.aaa.find({"_id":1,"m":{$elemMatch:{"_id":1}}})
{ "_id" : 1, "m" : [ { "_id" : 1, "a" : 1 }, { "_id" : 2, "a" : 2 } ] }
Using $elemMatch as query operator, it return all sub docs in 'm' !! Strange!
Use it as project operator:
db.aaa.find({"_id":1},{"m":{$elemMatch:{"_id":1}}})
{ "_id" : 1, "m" : [ { "_id" : 1, "a" : 1 } ] }
This is OK. Following this logic, use it as query operator in update will change all sub docs in 'm'. So I do:
db.aaa.update({"_id":1,"m":{$elemMatch:{"_id":1}}},{$set:{"m.$.a":3}})
db.aaa.find()
{ "_id" : 1, "m" : [ { "_id" : 1, "a" : 3 }, { "_id" : 2, "a" : 2 } ] }
It works in manner of as second example(project operator). This really confuse me.
Give me a explain
It Isn't strange, it's how it works.
You are using $elemMatch to match an element within an array contained in your document. That means it mactches the "document" and not the "array element", so it does not just selectively display only the array element that was matched.
What you can do, and how you used it in with the $set operator, is use a positional $ operator to indicate the matched "position" from your query side:
db.aaa.find({"_id":1},{"m":{$elemMatch:{"_id":1}}},{ "m.$": 1 })
And that will show you only one element of the array. But it is of course *still an array in the result shown, and you cannot cast it to a different type.
The other part of the usage is that this will only match once. And only the first match will be assigned to the positional operator.
So perhaps the most succinct explaination is you matching the "document that contains" the properties of the sub-document your specified in your query, and not just the "sub-document" itself.
See the documentation for more:
http://docs.mongodb.org/manual/reference/operator/projection/positional/
http://docs.mongodb.org/manual/reference/operator/query/elemMatch/

MongoDB Why this error : can't append to array using string field name: comments

I have a DB structure like below:
{
"_id" : 1,
"comments" : [
{
"_id" : 2,
"content" : "xxx"
}
]
}
I update a new subdocument in the comments feild. It is OK.
db.test.update(
{"_id" : 1, "comments._id" : 2},
{$push : {"comments.$.comments" : {_id : 3, content:"xxx"}}}
)
after that the DB structure:
{
"_id" : 1,
"comments" : [
{
"_id" : 2,
"comments" : [
{
"id" : 3,
"content" : "xxx"
}
],
"content" : "xxx"
}
]
}
But when I update a new subdocument in the comment field that _id is 3, There is a error:
db.test.update(
{"_id" : 1, "comments.comments.id" : 3},
{$push : {"comments.comments.$.comments" : {id : 4, content:"xxx"}}}
)
error message:
can't append to array using string field name: comments
Well, it makes total sense if you think about it. MongoDb has the advantage and the disadvantage of solving magically certain things.
When you query the database for a specific regular field like this:
{ field : "value" }
The query {field:"value"} makes total sense, it wouldn't in case value is part of an array but Mongo solves it for you, so in case the structure is:
{ field : ["value", "anothervalue"] }
Mongo iterates through all of them and matches "value" into the field and you don't have to think about it. It works perfectly.. at only one level, because it's impossible to guess what you want to do if you have multiple levels
In your case the first query works because it's the case in this example:
db.test.update(
{"_id" : 1, "comments._id" : 2},
{$push : {"comments.$.comments" : {_id : 3, content:"xxx"}}}
)
Matches _id in the first level, and comments._id at the second level, it gets an array as a result but Mongo is able to solve it.
But in the second case, think what you need, let's isolate the where clause:
{"_id" : 1, "comments.comments.id" : 3},
"Give me from the main collection records with _id:1" (one doc)
"And comments which comments inside have and id=3" (array * array)
The first level is solved easily, comments.id, the second is not possible due comments returns an array, but one more level is an array of arrays and Mongo gets an array of arrays as a result and it's not possible to push a document into all the records of the array.
The solution is to narrow your where clause to obtain an unique document in comments (could be the first one) but it's not a good solution because you never know what is the position of the document you're looking for, using the shell I think the only option to be accurate is to do it in two steps. Check this query that works (not the solution anyway) but "solves" the multiple array part fixing it to the first record:
db.test.update(
{"_id" : 1, "comments.0.comments._id" : 3},
{$push : {"comments.0.comments.$.comments" : {id : 4, content:"xxx"}}}
)

mongodb - how it processes $lt on subdocuments

I have a collection where one of the fields is a subdocument. I am confused how mongodb supports the $lt, $gt query operators on the complete subdocument.
sample:
db.test.insert({a:1, subdocA:{x:4, y:7, z:10}, b:10})
db.test.insert({a:9, subdocA:{x:2, y:70, z:5}, b:9})
db.test.insert({a:4, subdocA:{x:8, y:2, z:45}, b:19})
In the above collection, I see that mongodb supports a query like:
db.test.find({subdocA:{$lt:{x:6, y:5, z:25}})
In fact it also supports similar queries with $gt operator. It also supports sort({subdocA:1}) on the query.
I would like to know the "logic" it uses to compare the subdocuments and thereby process the $lt, $gt operators.
I see mongodb documentation about how exact matches are processed with subdocuments. But I don't see any documentation on how $lt, $gt are handled with subdocuments.
Thanks.
You have to specify the operator for each field, naming the field with a dot (.) to reach inside the embeeded document. The documentation about $gt hints at this.
So to query a subdocument on z lower than 20, you actually search for subdocA.z being lower than 20, like this :
> db.test.find({'subdocA.z':{$lt:20}}, {_id:0})
{ "a" : 1, "subdocA" : { "x" : 4, "y" : 7, "z" : 10 }, "b" : 10 }
{ "a" : 9, "subdocA" : { "x" : 2, "y" : 70, "z" : 5 }, "b" : 9 }
You can add other criteria in the same way, here with subdocA.x lower than 3 :
> db.test.find({'subdocA.z':{$lt:20}, 'subdocA.x':{$lt:3}}, {_id:0})
{ "a" : 9, "subdocA" : { "x" : 2, "y" : 70, "z" : 5 }, "b" : 9 }
Finally, you can mix and match fields from the "base" document :
> db.test.find({'subdocA.z':{$lt:20}, 'a':{$gt:3}}, {_id:0})
{ "a" : 9, "subdocA" : { "x" : 2, "y" : 70, "z" : 5 }, "b" : 9 }