According to this answer, removing an element from an array by index implies two distinct updates:
db.lists.update({}, {$unset : {"interests.0" : 1 }})
db.lists.update({}, {$pull : {"interests" : null}})
This works... but it introduces some complexity to make the operation atomic and prevent race conditions. Is there any update on that topic?
Yes and No, really. Consider the following array:
{ array: [ { a: "a", b: "b" }, { a:"a", b:"c" } ] }
So you could $pull the second element from here with the following statement:
db.collection.update({}, {$pull: { array: { a: "a", b: "b"} } })
And that would remove the matching document. But if did the same thing on this array:
{ array: [ { a: "a", b: "b" }, { a: "a", b:"c", c: "c" }, { a:"a", b:"c" } ] }
Then this would be the result:
{ array: [ { a: "a", b: "b" }] }
So the query matched two of the documents in the array, and pulled them both.
Unfortunately the seemingly obvious:
db.collection.update({}, {$pull: { array: { $elemMatch:{ a: "a", b: "b" } }} })
Just wont work. Which is a pity. In the too hard basket to parse at this time.
So the moral of the story is, if you really concerned about concurrency and race conditions for this type of update, .find() the matching element first, then update() with a statement that matches the whole document, as long as it will not also partially match another document in the array.
If you cannot do that, then the method you describe in the question is currently the only way.
There is a JIRA issue on this, but it is not exactly terribly popular.
Related
Consider the following documents:
{
_id: 1,
a: [{b:true},{b:true}]
},
{
_id: 2,
a: [{b:false},{b:true}]
},
{
_id: 3,
a: [{b:true}]
}
I'd like to write a query that will return all of the top level documents that have an array ("a") that contain only elements matching {b : true}. In this example, I'm expecting the first and third document to be returned, but not the second.
When writing the query like this, all 3 documents are returned..
{a : {b : true}}
Is there an operator that I'm missing? I've reviewed quite a few of them ($all) and I'm not sure which would match best for this use case
Thanks so much
Simply use $allElementsTrue on the array a.b.
db.collection.find({
$expr: {
"$allElementsTrue": "$a.b"
}
})
Here is the Mongo Playground for your reference.
Suppose we have three documents in a collection of mongodb like below:
{
book: "a"
},
{
book: "b"
},
{
book: "c"
}
I want a query that gives all documents that their book field value is a member of a given array. For example if the given array is ["a", "c", "d"], the query must return the first and the third document as result because "a" and "c" are members of array.
Is there a query for this to do it all at once or the only way is to loop over the given array and use a simple find query?
see $in operator
https://docs.mongodb.com/manual/reference/operator/query/in/
in your case
db.books.find( { book: { $in:["a", "c", "d"]} } )
Yes, this is possible by using the $in operator.
In the mongo shell this would look like
db.<collectionName>.find({ book: { $in: [ "a", "c", "d" ] }})
This question already has an answer here:
Update array with multiple conditions in mongodb
(1 answer)
Closed 4 years ago.
I don't know if it is possible.
I'm trying to do an automatic process to update all elements of a nested array in some documents. The array hasn't a fixed length.
Below is a simplified example of the collection:
{
"_id" : ObjectId("5ba2e413a4dd01725a658c63"),
"MyOwnID" : "123456789",
"MyArray" : [
{
Field1: 'FooName1',
Field2: 'FooSpec1',
FieldToUpdate: '...'
},
{
Field1: 'FooName1',
Field2: 'FooSpec2',
FieldToUpdate: '...'
},
{
... More elements ...
}
]
},
{
"_id" : ObjectId("5ba2e413a4dd01725a658c63"),
"MyOwnID" : "987654321",
"MyArray" : [
{
Field1: 'FooName1',
Field2: 'FooSpec1',
FieldToUpdate: '...'
},
{
Field1: 'FooName2',
Field2: 'FooSpec2',
FieldToUpdate: '...'
},
]
}
I tried and it worked for the first element:
Query for the second element:
db.getCollection('works').findOneAndUpdate(
{ MyOwnID: '123456789', '$and':[ { 'MyArray.Field1': 'FooName1' },{ 'MyArray.Field2': 'FooSpec1' } ] } ,
{ '$set': { 'MyArray.$.FieldToUpdate': 1234} }
)
But when I try to update the second element only the first is updated.
Query for the second element:
db.getCollection('works').findOneAndUpdate(
{ MyOwnID: '123456789', '$and':[ { 'MyArray.Field1': 'FooName1' },{ 'MyArray.Field2': 'FooSpec2' } ] } ,
{ '$set': { 'MyArray.$.FieldToUpdate': 4321} }
)
I tried with arrayFilters option and $elemMatch, both give me an error.
Any options?
You can try below query using $elemMatch
db.getCollection("works").findOneAndUpdate(
{
"MyOwnID": "123456789",
"MyArray": { "$elemMatch": { "Field1": "FooName1", "Field2": "FooSpec2" }}
},
{ "$set": { "MyArray.$.FieldToUpdate": 4321 }}
)
You tried with arrayFilters, but probably in a wrong way, becuse it's working with it. It's not very clear in mongoDB doc, but $[myRef] acts as a placeholder for arrayFilters. Knowing that, you can do this to achieve your goal :
db['01'].findOneAndUpdate(
{MyOwnID: '123456789'},
{$set:{"MyArray.$[object].FieldToUpdate":1234}},
{arrayFilters:[{ $and:[{'object.Field1': 'FooName1' },{ 'object.Field2': 'FooSpec1' }]}]}
)
Note that the unique document in arrayFilters is needed (with $and operator), because both conditions refer to the placeholder. If you put 2 conditions,
({arrayFilters:[{'object.Field1': 'FooName1' },{ 'object.Field2':
'FooSpec1' }]})
MongoDB will complain about two criteria with same base placeholder.
While the answer given by #Anthony Winzlet is right and works perfectly, it will only update the first array element matching conditions defined in $elemMatch, that's why i avoid to use it like that (unless having a unique index on including MyArray.Field1 and MyArray.Field2, you can't be sure that the matching element is unique in your array)
I have this following strtucture
{
_id: ".....",
a: {
a1:".......",
b: [
b1: {
b11: "......",
b12: "......",
},
b2: {
b21: "......",
b22: "......",
d: {},
},
c:{
c1: {
......
},
d: {
}
}
]
}
}
here I want to check if property d exists or not inside b, it may exists in multiple objects inside b, if exists pull the d object from record.
Note: There might be a chance that property d exists multiple times inside b1 and b2, In this case I want to remove it from all objects
I tried like
Coll.find({ 'a.b': { $elemMatch: { 'c': { d: { $exists: true } } } } })
but it is not returning anything although there are record, any help appreciated.
I want to pull that data from record too.
Thanks.
UPDATE
Coll.find({ 'a.b.c.d'{ $exists: true } })
that worked for me but still no idea how to use positional operator to pull the value from record
Please take a little more time forming your questions to make them easier to answer: your example data has key-values inside a list (invalid), your question mentions $elemMatch which is a list operator, you talk about removing things but never follow up that thought, and then your UPDATE switches gears and implies it is an object hierarchy.
Taking a hint from your UPDATE, I created some valid data - paste this into mongo shell (perhaps in the test database):
db.test.insert({
a: {
a1: "a1",
b: {
b1: {
b11: "b11",
b12: "b12",
},
b2: {
b21: "b21",
b22: "b22",
d: {},
},
c: {
c1: {
c1: "......"
},
d: {
d1: "woo hoo, struck gold"
}
}
}
}
})
Inside the mongo shell, everything is javascript so "positional operators" are the dot and array subscript operator. A mongo find() returns an array of documents. If you want the the d document (object) from the first doc returned from the query
db.test.find({'a.b.c.d': {$exists: true}})[0].a.b.c.d
produces
{
"d1": "woo hoo, struck gold"
}
UPDATE: Adding detail in response to comment.
If you want to remove the a.b.c.d sub-document, use $unset
// remove sub-document a.b.c.d
db.test.update({}, {$unset: {'a.b.c.d': ''}});
// look at the document to verify that a.b.c.d is removed
db.test.find();
If I have the following documents:
{a: {x:1}} // without array
{a: [{x:1}]} // with array
Is there a way to query for {'a.x':1} that will return the first one but not the second one? IE, I want the document where a.x is 1, and a is not an array.
Please note that future version of MongoDB would incorporate the $isArray aggregation expression. In the meantime...
...the following code will do the trick as the $elemMatch operator matches only documents having an array field:
> db.test.find({"a.x": 1, "a": {$not: {$elemMatch: {x:1}}}})
Given that dataset:
> db.test.find({},{_id:0})
{ "a" : { "x" : 1 } }
{ "a" : [ { "x" : 1 } ] }
{ "a" : [ { "x" : 0 }, { "x" : 1 } ]
It will return:
> db.test.find({"a.x": 1, "a": {$not: {$elemMatch: {x:1}}}}, {_id:0})
{ "a" : { "x" : 1 } }
Please note this should be considered as a short term solution. The MongoDB team took great cares to ensure that [{x:1}] and {x:1} behave the same (see dot-notation or $type for arrays). So you should consider that at some point in the future, $elemMatch might be updated (see JIRA issue SERVER-6050). In the meantime, maybe worth considering fixing your data model so it would no longer be necessary to distinguish between an array containing one subdocument and a bare subdocument.
You can do this by adding a second term that ensures a has no elements. That second term will always be true when a is a plain subdoc, and always false when a is an array (as otherwise the first term wouldn't have matched).
db.test.find({'a.x': 1, 'a.0': {$exists: false}})