Return array index from $elemMatch query - mongodb

Say I have a document like this
{
title : 'myTitle',
favorites : [{name : 'text', number : 6}, {name : 'other', number : 4}]
}
I would like to return the array index (if any) from where the embedded document was retrieved within the favorites array.
Say I have the following query
{title : 'myTitle'}
and the projection
{favorites : {$elemMatch : {name : 'text', number : 6} }}
if the projection returns the document AND it contains a favorites array with the sub document, is there a way to know at which index that sub document was found? Which in this case is index 0.
The reason I would like the index is because as soon as I retrieve the document, I proceed to update it, and it would boost the performance if I had a specific index to update instead of using $elemMatch again which would cause mongo to iterate through all the array entries until finding the document that matches.

Unfortunately, it is not possible using the $elemMatch operator. If you are using mongodb 3.2, you could make use of the $unwind operator and aggregate instead of doing a find()
db.collection.aggregate([
{$match:{"favorites.name":"text","favorites.number":6}},
{$unwind:{"path":"$favorites","includeArrayIndex":"index"}},
{$match:{"favorites.name":"text","favorites.number":6}}
])
The document would be returned, with its array index in the field - index. If you want the entire document, with other array elements, you would have to $group, after $unwind instead of $match.
For previous versions, iterating the array in the client side, and getting the sub document's index would be the way to go.

Related

Using $match after a $lookup in MongoDB

I have two collections and I want to get fields from both, so I'm using $lookup in an aggregation pipeline.
This works fine and returns all the documents with an extra field, an array with 0 or 1 elements (an object). If 0 elements, it means that the JOIN(in SQL world) didn't return anything. If 1 element, it means that there was a match and the element in an object with the fields of the second collection.
Now that I have those results, I'd like to use $match in order to filter some of the results.
In order to use $match I first want to use $unwind on that new extra field in order to extract the array. The problem is once I insert the $unwind stage, the result of the query is a single document.
Why is this happening? How can I $unwind and then $match all the documents I got from the $lookup stage?
assume we have documents after lookup:
{doc:{_id:1, lookupArray:[{doc:1},{doc:2}]}}
and
{doc:{_id:2, lookupArray:[/* empty */]}}
when we $unwind without any options we will get:
{doc:{_id:1, lookupArray:{doc:1}}}
{doc:{_id:1, lookupArray:{doc:2}}}
null
and when we specify
{ $unwind: { path: "$array", preserveNullAndEmptyArrays: true } }
then we will get:
{doc:{_id:1, lookupArray:{doc:1}}}
{doc:{_id:1, lookupArray:{doc:2}}}
{doc:{_id:2, lookupArray:[/* empty */]}}
So when you want to perform a search for a value doc from lookupArray, $match will look like this:
{$match:{'lookupArray.doc':2}}
Any comments welcome!

mongodb , wildcard in $in

I have a mongodb query where i want to get documents if a field has particular value.
db.collection.find({key:{$in:['value1','value2']}}) if i run above command i get documents containing either 'value1' or 'value2'. but lets just say there are no values. and i search db.collection.find({key:{$in:[]}}), nothing is displayed. and db.collection.find({key:{$in:[*]}}) gives unexpected token* which wild card do i use in $in to show all results.?
I think this is logically consistent behavior for $in. The query
db.collection.find({ "key" : { "$in" : [] } })
could be translated as "find all the documents where the value of key is one of the values contained in the array []". Since there are no values in the array [], there are no matching documents. If you want to find all of the extant values for key, use .distinct to return them as an array:
db.collection.distinct("key")
.distinct will use an index if possible.
If you want a query to match all documents, omit the query selector from .find:
db.collection.find()
as suggested in the comments.

Mongodb Indexing suggestions

All,
I have a mangodb collection with below fields.
_ID
Title
Description
Tags , array
I have created 2 index on _id and tags field. I have created index for people to search the content with help of keywords.
I have created the index with tags:-1 to show the latest inserted records to show first. But even after that it is showing in the ascending order of _id.
How to create the index on tags field to show the last inserted to show first at the same time it should allow me to search on tags field faster .
If the _id field is the default ObjectId which reflects the insertion order, and you want to query all the documents with a specific Tags by descending insertion order, you can use the query as below:
find({ Tags : $value }).sort({ _id : -1 })
For this query, you can create a compound index on { Tags : 1, _id : -1 }. All the documents with the same Tags will be sorted in descending insertion order and this index should work well for this query.
Please note that if you are doing range query on Tags, like:
find({ Tags : { $in : [ $value1, $value2 ] }}).sort({ _id : -1 })
find({ Tags : { $gt : $value}}).sort({ _id : -1})
It wouldn’t be able to use the index to sort the result documents, and will need to sort the results in memory. You can run the query with .explain(true) to check the query plan. If scanAndOrder is true, it means the query cannot use the order of documents in the index for returning sorted results.
There are also some documents and blogs relate to indexes and that I'd recommend reading:
http://docs.mongodb.org/manual/tutorial/sort-results-with-indexes/
http://emptysqua.re/blog/optimizing-mongodb-compound-indexes/
http://blog.mongolab.com/2012/06/cardinal-ins/

MongoDB, using group by aggregate framework to get unique strings

I'm trying to use the aggregation framework to group a lot of strings together to indentify the unique ones. I must also keep some information about the rest of the fields. This would be analogous to me using the * operator in mysql with a group by statement.
SELECT *
FROM my_table
GROUP BY field1
I have tried using the aggregation framework, and it works fine just to get unique fields.
db.mycollection.aggregate({
$group : { _id : "$field1"}
})
What if I want the other fields that went with that. MySQL would only give me the first one that appeared in the group (which I'm fine with). Thats what I thought the $first operator did.
db.mycollection.aggregate({
$group : {
_id : "$field1",
another_field : {$first : "$field2"}
}})
This way it groups by field1 but still gives me back the other fields attached to document. When I try this I get:
exception: aggregation result exceeds maximum document size (16MB)
Which I have a feeling is because it is returning the whole aggregation back as one document. Can I return it as another json array?
thanks in advance
You're doing the aggregation correctly, but as the error message indicates, the full result of the aggregate call cannot be larger than 16 MB.
Work-arounds would be to either add a filter to reduce the size of the result or use map-reduce instead and output the result to another collection.
If you unique values of the result does not exceed 2000 you could use group() function like
db.mycollection.group( {key : {field1 : 1, field2 : 1}}, reduce: function(curr, result){}, initial{} })
Last option would be map reduce:
db.mycollection.mapReduce( function() { emit( {field1 :1, field2: 1}, 1); }, function(key, values) { return 1;}, {out: {replace: "unique_field1_field2"}})
and your result would be in "unique_field1_field2" collection
Another alternative is use the distinct function:
db.mycollection.distinct('field1')
This functions accepts a second argument, a query, where you can filter the documents.

How do I do a "NOT IN" query in Mongo?

This is my document:
{
title:"Happy thanksgiving",
body: "come over for dinner",
blocked:[
{user:333, name:'john'},
{user:994, name:'jessica'},
{user:11, name: 'matt'},
]
}
What is the query to find all documents that do not have user 11 in "blocked"?
You can use $in or $nin for "not in"
Example ...
> db.people.find({ crowd : { $nin: ["cool"] }});
I put a bunch more examples here: http://learnmongo.com/posts/being-part-of-the-in-crowd/
Since you are comparing against a single value, your example actually doesn't need a NOT IN operation. This is because Mongo will apply its search criteria to every element of an array subdocument. You can use the NOT EQUALS operator, $ne, to get what you want as it takes the value that cannot turn up in the search:
db.myCollection.find({'blocked.user': {$ne: 11}});
However if you have many things that it cannot equal, that is when you would use the NOT IN operator, which is $nin. It takes an array of values that cannot turn up in the search:
db.myCollection.find({'blocked.user': {$nin: [11, 12, 13]}});
Try the following:
db.stack.find({"blocked.user":{$nin:[11]}})
This worked for me.
See http://docs.mongodb.org/manual/reference/operator/query/nin/#op._S_nin
db.inventory.find( { qty: { $nin: [ 5, 15 ] } } )
This query will
select all documents in the inventory collection where the qty field
value does not equal 5 nor 15. The selected documents will include
those documents that do not contain the qty field.
If the field holds an array, then the $nin operator selects the
documents whose field holds an array with no element equal to a value
in the specified array (e.g. , , etc.).