Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

$projection vs $elemMatch

Tags:

mongodb

What is the functional difference between:

db.docs.find({ 'a.b': 'c' }, { 'a.$': 1 })

and

db.docs.find({ 'a.b': 'c' }, { 'a': { $elemMatch: { b: 'c' } } })

Is $elemMatch redundant? How would indexes change?

like image 948
paulkon Avatar asked Feb 03 '14 03:02

paulkon


2 Answers

The difference in projection usage is somewhat subtle. In your example usage, these should be equivalent queries (in terms of index usage) but the $elemMatch example unnecessarily repeats the query criteria. The $ projection would be a more sensible choice for this example.

An essential difference noted in the documentation is the array field limitation for $ projections:

Since only one array field can appear in the query document, if the array contains documents, to specify criteria on multiple fields of these documents, use the $elemMatch operator.

Some further notes on the differences in the projection operators below ...

The positional ($) projection operator:

  • limits the contents of an array field that is included in the query results to contain the first element that matches the query document.

  • requires that the matching array field is included in the query criteria

  • can only be used if a single array field appears in the query criteria

  • can only be used once in a projection

The $elemMatch projection operator

  • limits the contents of an array field that is included in the query results to contain only the first array element that matches the $elemMatch condition.

  • does not require the matching array to be in the query criteria

  • can be used to match multiple conditions for array elements that are embedded documents

The $elemMatch query operator

Note that there is also an $elemMatch query operator which performs similar matching, but in the query rather than the results projection. It's not uncommon to see this used in combination with a $ projection.

Borrowing an example from the docs where you might use both:

db.students.find(
    // use $elemMatch query operator to match multiple criteria in the grades array
    { grades: {
        $elemMatch: {
            mean:  { $gt: 70 },
            grade: { $gt: 90 }
        }
    }},

    // use $ projection to get the first matching item in the "grades" array
    { "grades.$": 1 }
)
like image 176
Stennie Avatar answered Oct 16 '22 21:10

Stennie


If you use $elemMatch alone, in find projection,

$elemMatch will not filter documents (conditions not applied)

Suppose, 'students' contains the following documents

{ "_id" : 1, "semester" : 1, "grades" : [ 70, 87, 90 ] }
{ "_id" : 2, "semester" : 1, "grades" : [ 90, 88, 92 ] }
{ "_id" : 3, "semester" : 1, "grades" : [ 85, 100, 90 ] }
{ "_id" : 4, "semester" : 2, "grades" : [ 79, 85, 80 ] }
{ "_id" : 5, "semester" : 2, "grades" : [ 88, 88, 92 ] }
{ "_id" : 6, "semester" : 2, "grades" : [ 95, 90, 96 ] }
{
    "_id" : 7,
    "semester" : 3,
    "grades" : [
        {
            "grade" : 80,
            "mean" : 75,
            "std" : 8
        },
        {
            "grade" : 85,
            "mean" : 90,
            "std" : 5
        },
        {
            "grade" : 90,
            "mean" : 85,
            "std" : 3
        }
    ]
}
{
    "_id" : 8,
    "semester" : 3,
    "grades" : [
        {
            "grade" : 92,
            "mean" : 88,
            "std" : 8
        },
        {
            "grade" : 78,
            "mean" : 90,
            "std" : 5
        },
        {
            "grade" : 88,
            "mean" : 85,
            "std" : 3
        }
    ]
}

the below query will return all the documents in collection,

db.students.find({},{ grades: { $elemMatch: { mean: 90 } } }  ).pretty()

where as, below query returns only the matching records

db.students.find({"grades.mean":90},{"grades.$":1}).pretty()

i hope Indexes do not make any difference in either queries

like image 1
Stephen Avatar answered Oct 16 '22 22:10

Stephen