mongodb $elemMatch - mongodb

According to mongodb doc, syntax for $elemMatch would be,
t.find( { x : { $elemMatch : { a : 1, b : { $gt : 1 } } } } )
I have tried and it works fine.
The above means that, it can find if an object {a:1, b:'more than 1'} exist in the array x.
I have a requirement, where I need to figure out, if all the objects in an array exist in the database or not.
for example, let's say I have an array,
a=[{a:1, b:2},{a:3, b:4}, {a:5, b:6}]
and I need to find out if x contains all of them.
t.find( { x : { $elemMatch : { a : {$all:[1]}, b : {$all:[2]} } } } ) and it finds out all x containing {a:1, b:2}
But if I try, t.find( { x : { $elemMatch : { a : {$all:[1,3]}, b : {$all:[2,4]} } } } ), it fails. I know this is not correct.
Is there any way I can achieve this ?
Ideallt, it should be,
t.find( { x : { $elemMatch : {$all:[ {a:1, b:2}, {a:3, b:4}] } } )
I tried, it does not work.

t.find({$and:[{a:{$elemMatch:{a:1, b:2}}}, {a:{$elemMatch:{a:3, b:4}}}, {a:{$elemMatch:{a:5, b:6}}}]})
It isn't a particularly high performance option though.

You can not use elemMatch for this, but you can simply just create a query which checks whether a matches the whole array:
db.items.insert({ 'foo' : 1, 'a' : [{a:1, b:2},{a:3, b:4}, {a:5, b:6}]});
db.items.insert({ 'foo' : 1, 'a' : [{a:1, b:2},{a:3, b:4}, {a:8, b:7}]});
db.items.find({'a': [{a:1, b:2},{a:3, b:4}, {a:8, b:7}]});
{ "_id" : ObjectId("4f3391856e196eca5eaa7518"), "foo" : 1, "a" : [ { "a" : 1, "b" : 2 }, { "a" : 3, "b" : 4 }, { "a" : 8, "b" : 7 } ] }
However, for this to work the order of the elements in the array need to be the same for the document and the query. The following will not find anything:
db.items.find({'a': [{a:3, b:4},{a:1, b:2}, {a:8, b:7}]});
(Because {a:3, b:4} and {a:1, b:2} are swapped).

How about this:
db.items.find({x : { $all: [
{$elemMatch: {a: 1, b: 2}},
{$elemMatch: {a: 3, b: 4}},
{$elemMatch: {a: 5, b: 6}}
]}}
Check out the Mongo docs.
Also, note the docs warning:
In the current release, queries that use the $all operator must scan
all the documents that match the first element in the query array. As
a result, even with an index to support the query, the operation may
be long running, particularly when the first element in the array is
not very selective.

Related

MongoDB Aggregation - Does $unwind order documents the same way as the nested array order

I am wandering whether using $unwind operator in aggregation pipeline for document with nested array will return the deconstructed documents in the same order as the order of the items in the array.
Example:
Suppose I have the following documents
{ "_id" : 1, "item" : "foo", values: [ "foo", "foo2", "foo3"] }
{ "_id" : 2, "item" : "bar", values: [ "bar", "bar2", "bar3"] }
{ "_id" : 3, "item" : "baz", values: [ "baz", "baz2", "baz3"] }
I would like to use paging for all values in all documents in my application code. So, my idea is to use mongo aggregation framework to:
sort the documents by _id
use $unwind on values attribute to deconstruct the documents
use $skip and $limit to simulate paging
So the question using the example described above is:
Is it guaranteed that the following aggregation pipeline:
[
{$sort: {"_id": 1}},
{$unwind: "$values"}
]
will always result to the following documents with exactly the same order?:
{ "_id" : 1, "item" : "foo", values: "foo" }
{ "_id" : 1, "item" : "foo", values: "foo2" }
{ "_id" : 1, "item" : "foo", values: "foo3" }
{ "_id" : 2, "item" : "bar", values: "bar" }
{ "_id" : 2, "item" : "bar", values: "bar2" }
{ "_id" : 2, "item" : "bar", values: "bar3" }
{ "_id" : 3, "item" : "baz", values: "baz" }
{ "_id" : 3, "item" : "baz", values: "baz2" }
{ "_id" : 3, "item" : "baz", values: "baz3" }
I also asked the same question in the MongoDB community forum . An answer that confirms my assumption was posted from a member of MongoDB stuff.
Briefly:
Yes, the order of the returned documents in the example above will always be the same. It follows the order from the array field.
In the case that you do run into issues with order. You could use includeArrayIndex to guarantee order.
[
{$unwind: {
path: 'values',
includeArrayIndex: 'arrayIndex'
}},
{$sort: {
_id: 1,
arrayIndex: 1
}},
{ $project: {
index: 0
}}
]
From what I see at https://github.com/mongodb/mongo/blob/0cee67ce6909ca653462d4609e47edcc4ac5c1a9/src/mongo/db/pipeline/document_source_unwind.cpp
The cursor iterator uses getNext() method to unwind an array:
DocumentSource::GetNextResult DocumentSourceUnwind::doGetNext() {
auto nextOut = _unwinder->getNext();
while (nextOut.isEOF()) {
.....
// Try to extract an output document from the new input document.
_unwinder->resetDocument(nextInput.releaseDocument());
nextOut = _unwinder->getNext();
}
return nextOut;
}
And the getNext() implemenation relies on array's index:
DocumentSource::GetNextResult DocumentSourceUnwind::Unwinder::getNext() {
....
// Set field to be the next element in the array. If needed, this will automatically
// clone all the documents along the field path so that the end values are not shared
// across documents that have come out of this pipeline operator. This is a partial deep
// clone. Because the value at the end will be replaced, everything along the path
// leading to that will be replaced in order not to share that change with any other
// clones (or the original).
_output.setNestedField(_unwindPathFieldIndexes, _inputArray[_index]);
indexForOutput = _index;
_index++;
_haveNext = _index < length;
.....
return _haveNext ? _output.peek() : _output.freeze();
}
So unless there is anything upstream that messes with document's order the cursor should have unwound docs in the same order as subdocs were stored in the array.
I don't recall how merger works for sharded collections and I imagine there might be a case when documents from other shards are returned from between 2 consecutive unwound documents. What the snippet of the code guarantees is that unwound document with next item from the array will never be returned before unwound document with previous item from the array.
As a side note, having million items in an array is quite an extreme design. Even 20-bytes items in the array will exceed 16Mb doc limit.

mongodb apply $not to $and - is there some ideas?

I'm trying to do something like this :
select * from table where not (a=3 and b=4 and c=3 or x=4)
I would expect this to work:
db.table.find( {
$not : {
$or : [
{ $and : [ { a : 3 },
{ b : 4 },
{ c : 3 }
] } ,
{ x : 4 }
}
} )
But it is not work.
I have read this article
And something like this: {a : { $ne : 3}, b : { $ne : 4} ...}. does
not suit me.
Because: my program takes DIFFERENT queries like this (a=3 and b=4 and c=3 or x=4) from users (queries to multilevel embedded objects and arrays).
And to write procedure, wich automatically apply $not to those queries looks
long and thankless task. have you any ideas?
PS Why mongo does not have the way simply to do that?
for example, to find all the documents that match the condition, and to take from the collection of the remaining documents

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 upsert in updating an array element

Want to upsert in object properties in a array of a document
Consider a document in collection m
{ "_id" : ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x" : [
{ "id":0.0, "name":"aaa"},{ "id":1.0, "name":"bbb"}
]
}
Want to add age:100 to { "id":0.0, "name":"aaa"} .
Not just age .. But but provision for upsert in the array element {}. So it can contain {age:100,"city":"amd"} (since i am getting this from the application service)
Was trying this... But did not worked as it replaced the entire array element
db.m.update({_id:ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x" : {
"$elemMatch" : {
"id" : 0.0
}
}},
{
$set : {
"x.$" : {
"age": 100
}
}
},
{upsert: true}
)
Changed the document to (which i did not wanted)
{ "_id" : ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x" : [
{ "age":100},{ "id":1.0, "name":"bbb"}
]
}
Is this possible without changing schema.
$set : {"x.$" : {"age": 100}}
x.$ sets the entire matched array element to {age: 100}
This should work:
db.m.update({_id:ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x.id": 0.0}, {$set: {"x.$.age": 100 }});
Using elemMatch:
db.test.update({x: {$elemMatch: {id: 1}}},{$set: {"x.$.age": 44}})
Note that the upsert option here, is redundant and wouldn't work if the id isn't present in x because the positional operator $ doesn't support upserting.
This is not possible without changing schema. If you can change schema to use an object to store your items (rather than an array), you can follow the approach I outlined in this answer.

Using $not in mongodb

I'm trying to do something like this :
select * from table where not (a=3 and b=4 and c=3 or x=4)
I would expect this to work:
db.table.find({
$not: {
$or: [
{
$and: [
{ a: 3 },
{ b: 4 },
{ c: 3 }
]
},
{ x: 4 }
]
}
})
But it gives me an error:
error: { "$err" : "invalid operator: $and", "code" : 10068 }
Is there another way to express it in mongodb?
Firstly, I don't think you mean "and" as a field will never 3 and 4 at the same time - it can only be 3 or 4. So, assuming you do want "documents where b is not 3 or 4, then you can use $nin (not in) like this:
db.table.find({b:{$nin: [3,4]}});
Using { $not : { $and : []} } will not work ($not is not like other operators, can only be applied to negate the check of other operators).
$and is not the problem here, this also doesn't work (though without reporting any errors):
{ $not : { a : {$gt : 14} }
you'd have to rewrite it to
{ a : { $not : {$gt : 14} }
Coming back to your query:
`not (a=3 and b=4 and c=3 or x=4)`
is equivalent to:
a!=3 and b!=4 and c!=3 or x!=4
and that you can do in mongo:
{a : { $ne : 3}, b : { $ne : 4} ...}