MongoDB: Find documents with key in subdocument - mongodb

The $in operator works with arrays.
Is there an equivalent for dictionaries?
The following code creates two test documents and finds the ones containing one of the listed values in the array documents, but doesn't find the ones containing the same values in the sub-documents.
> use test
> db.stuff.drop()
> db.stuff.insertMany([{lst:['a','b'],dic:{a:1,b:2}},{lst:['a','c'],dic:{a:3,c:4}}])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("595bbe8b3b0518bcca4b1530"),
ObjectId("595bbe8b3b0518bcca4b1531")
]
}
> db.stuff.find({lst:{$in:['b','c']}},{_id:0})
{ "lst" : [ "a", "b" ], "dic" : { "a" : 1, "b" : 2 } }
{ "lst" : [ "a", "c" ], "dic" : { "a" : 3, "c" : 4 } }
> db.stuff.find({dic:{$in:['b','c']}},{_id:0})
>
EDIT (in response to the answer below)
Using the list as suggested in the answer below prevents me from finding the desired element. For example, after executing both the insertMany above in this question and below in the answer, the following can be done with a dictionary, not with a list (or am I missing something?):
> x=db.stuff.findOne({lst:{$in:['b','c']}},{_id:0})
{ "lst" : [ "a", "b" ], "dic" : { "a" : 1, "b" : 2 } }
> x
{ "lst" : [ "a", "b" ], "dic" : { "a" : 1, "b" : 2 } }
> x.dic.a
1
> x.dic.b
2

For subdocuments, there's no exact equivalent to $in. You could use the $exists query operator combined with $or:
db.stuff.find({$or:[
{'dic.b': {$exists: true}},
{'dic.c': {$exists: true}}
]})
The recommended approach, however, is to change your schema, so that the keys and values are changed into an array of {key: "key", value: 123} subdocuments:
db.stuff.insertMany([
{dic: [{key: 'a', value: 1}, {key: 'b', value: 2}]},
{dic: [{key: 'a', value: 3}, {key: 'c', value: 4}]}
])
Then you can use $in to find documents with certain keys:
db.stuff.find({'dic.key': {$in: ['a', 'b']}})
The especially good thing about this new schema is you can use an index for the $in query:
db.stuff.createIndex({'dic.key': 1})
A disadvantage, as you point out above, is that simple element access like x.dic.a no longer works. You need to do a bit of coding in your language. E.g. in Javascript:
> var doc = {dic: [{key: 'a', value: 3}, {key: 'c', value: 4}]}
> function getValue(doc, key) {
... return doc.dic.filter(function(elem) {
... return elem.key == key;
... })[0].value;
... }
> getValue(doc, "a")
3
> getValue(doc, "c")
4

Related

MongoDB: querying for completion in documents containing an array of objects

I have the following documents inside a folders collection:
folders: [
{ _id : 1,
docs : [
{ foo : 1,
bar : undefined},
{ foo : 3,
bar : 3}
]
},
{ _id : 2,
docs : [
{ foo : 2,
bar : 2},
{ foo : 3,
bar : 3}
]
},
{ _id : 3,
docs : [
{ foo : 2},
{ foo : 3,
bar : 3}
]
},
{ _id : 4,
docs : [
{ foo : 1 }
]
},
{ _id : 5,
docs : [
{ foo : 1,
bar : null }
]
}
]
I need to be able to query the documents that do not have an undefined value, null value, or non-existent value for docs.bar. In the case above, the query should only return the document with _id: 2. I currently have a solution but I was wondering if there is a better way to query the documents.
My current solution:
db.folders.find({$nor: [{"docs.bar": { $exists: false }}]})
This ...
db.folder.find({"docs.bar": {$exists: true}, "docs.bar": {$ne: null}})
... will return only those entries for which at least one of the sub documents in the docs array has a populated bar attribute. Note: in this query the two predicates are ANDed, I think that matches your requirements, it certainly returns the document with _id: 2 from the set you supplied.

MongoDB, how to use document as the smallest unit to search the document in array?

Sorry for the title, but I really do not know how to make it clear. But I can show you.
Here I have insert two document
> db.test.find().pretty()
{
"_id" : ObjectId("557faa461ec825d473b21422"),
"c" : [
{
"a" : 3,
"b" : 7
}
]
}
{
"_id" : ObjectId("557faa4c1ec825d473b21423"),
"c" : [
{
"a" : 1,
"b" : 3
},
{
"a" : 5,
"b" : 9
}
]
}
>
I only want to select the first document with a value which is greater than 'a' and smaller than 'b', like '4'.
But when i search, i cannot get the result i want
> db.test.find({'c.a': {$lte: 4}, 'c.b': {$gte: 4}})
{ "_id" : ObjectId("557faa461ec825d473b21422"), "c" : [ { "a" : 3, "b" : 7 } ] }
{ "_id" : ObjectId("557faa4c1ec825d473b21423"), "c" : [ { "a" : 1, "b" : 3 }, { "a" : 5, "b" : 9 } ] }
>
Because '4' is greater than the '"a" : 1' and smaller than '"b" : 9' in the second document even it is not in the same document in the array, so the second one selected.
But I only want the first one selected.
I found this http://docs.mongodb.org/manual/reference/operator/query/elemMatch/#op._S_elemMatch, but it seems the example is not suitable for my situation.
You would want to
db.test.findOne({ c: {$elemMatch: {a: {$lte: 4}, b: {$gte: 4} } } })
With your query, you are searching for documents that have an object in the 'c' array that has a key 'a' with a value <= 4, and a key 'b' with a value >= 4.
The second record is return because c[0].a is <= 4, and c[1].b is >= 4.
Since you specified you wanted to select only the first document, you would want to do a findOne() instead of a find().
Use $elemMatch as below :
db.test.find({"c":{"$elemMatch":{"a":{"$lte":4},"b":{"$gte":4}}}})
Or
db.test.find({"c":{"$elemMatch":{"a":{"$lte":4},"b":{"$gte":4}}}},{"c.$":1})

Query to match a non-empty array with a certain value

For example
> db.test.insert( { 'a':5 } )
> db.test.insert( { 'a': [5] } )
> db.test.find({ 'a':5})
{ "_id" : ObjectId("53e2b4366c9ef5cceb327e01"), "a" : 5 }
{ "_id" : ObjectId("53e2b43b6c9ef5cceb327e02"), "a" : [ 5 ] }
But, I only want to be able to match the first document.
The simplest way is to test for the presence of an array element using $exists and "dot notation":
db.test.find({ "a": 5, "a.0": { "$exists": false } })
Says find "a" equal to 5, but the first array element in "a" cannot exist.

mongodb join-like query with two collections and a where clause

Suppose we have following collections in a database:
db.documents.insert([{'name': 'A'}, {'name': 'B'}, {'name': 'C'}])
db.fragments.insert([{'value:': 'A1', doc_name: 'A'}, {'value:': 'A2', doc_name: 'A'},
{'value:': 'B1', doc_name: 'B'}, {'value:': 'B2', doc_name: 'B'},
{'value:': 'C1', doc_name: 'C'}, {'value:': 'C2', doc_name: 'C'}])
where documents collection stores the names of the documents (and other stuff omitted in this example), fragments collections refers by doc_name to a document related to the fragment.
Now, if I only want to consider a subset of documents
> db.documents.find().limit(2)
{ "_id" : ObjectId("52d1a3bf49da25160ad6f076"), "name" : "A" }
{ "_id" : ObjectId("52d1a3bf49da25160ad6f077"), "name" : "B" }
then how can I see the fragments of associated to these selected documents, so I would get
{ "_id" : ObjectId("52d1a3bf49da25160ad6f079"), "value:" : "A1", "doc_name" : "A" }
{ "_id" : ObjectId("52d1a3bf49da25160ad6f07a"), "value:" : "A2", "doc_name" : "A" }
{ "_id" : ObjectId("52d1a3bf49da25160ad6f07b"), "value:" : "B1", "doc_name" : "B" }
{ "_id" : ObjectId("52d1a3bf49da25160ad6f07c"), "value:" : "B2", "doc_name" : "B" }
As a solution, I was thinking that I should store the document names in an array, something like var docnames = ??? such that
> docnames
[ "A", "B" ]
and then trying to use this array in a where clause, something like
> db.fragments.find({$where: function(x) { return (this.doc_name in docnames)}})
error: {
"$err" : "ReferenceError: docnames is not defined near 'c_name in docnames)}' ",
"code" : 16722
}
But as I am very new to mongodb, so I am having trouble figuring it out. I believe this could be done as a one-liner as well.
db.fragments.find( { 'doc_name': { $in : ['A' , 'B'] } } );
Execute this commands in mongo:
var f = db.documents.find().limit(2) , n = [];
for (var i = 0; i < f.length(); i++) n.push(f[i]['name']);
db.fragments.find( { 'doc_name': { $in : n } } );

Mongodb: find embedded element missing some key

I have a document with an embedded collection, but few elements are missing a key and I have to find all those elements. Here is an example:
var foo = {name: 'foo', embedded: [{myKey: "1", value: 3}, {myKey: "2", value: 3}]}
db.example.insert(foo)
var bar = {name: 'bar', embedded: [{value: 4}, {myKey: "3", value: 1}]}
db.example.insert(bar)
I need a query that returns the 'bar' object because one of its embedded doesn't have the key 'myKey'.
I try to use the $exists, but it returns only if ALL embedded elements are missing the key
db.example.find({'embedded.myKey': {$exists: true}}).size()
// -> 2
db.example.find({'embedded.myKey': {$exists: false}}).size()
// -> 0
How can I find the documents that at least one embedded element is missing the key 'myKey'?
If 'value' is always present, then you can try this command
db.example.find({ embedded : { $elemMatch : { value : {$exists : true}, myKey : {$exists : false}} }})
{ "_id" : ObjectId("518bbccbc9e49428608691b0"), "name" : "bar", "embedded" : [ { "value" : 4 }, { "myKey" : "3", "value" : 1 } ] }