7

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?

paulkon
  • 1,755
  • 2
  • 20
  • 34

2 Answers2

10

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 }
)
Stennie
  • 63,885
  • 14
  • 149
  • 175
1

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

Stephen
  • 3,359
  • 4
  • 20
  • 30
  • 1
    I think you are confusing projection with queries :). For a fair comparison, you need to have a query in both of your find examples. – Stennie Feb 03 '14 at 04:29
  • May be:( , if $elemMatch(projection) doesnt filter documents, and is only used to return first matching element, then why to use $elemMatch with nested queries, we can simply use $ projection – Stephen Feb 03 '14 at 04:54
  • The $elemMatch projection "can be used to match multiple conditions for array elements that are embedded documents" as @Stennie pointed out in his answer. To be honest I wish the projection docs would provide a more direct comparison as Stennie outlined. – paulkon Feb 03 '14 at 05:22
  • I answered with a bit more elaboration on the differences. Typically you probably want to use a `$` projection, but there are some use cases where `$elemMatch` is more appropriate (for example, matching multiple conditions for the array elements). Also note that projection is done at the *document* level when results are returned. With your first example with `$elemMatch` on all documents, in the results you get will see either the `_id` plus the first matching `grades` array element, or `_id` alone (since there is no matching element to project). – Stennie Feb 03 '14 at 05:28
  • @paulkon: Agreed, the docs should include this sort of direct comparison. I'll submit a suggestion for same :). – Stennie Feb 03 '14 at 05:30