Skip to content

DOCS-2643: Clarify distinction between positional $ and projection $elem... #1981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions source/includes/fact-positional-projection-vs-elemmatch.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Both the :projection:`$` operator and the :projection:`$elemMatch` operator project
a subset of elements from an array based on a condition.

The :projection:`$` operator projects the array elements based on some condition
from the query statement.

The :projection:`$elemMatch` projection operator takes an explicit condition
argument. This allows you to project based on a condition not in the query, or
if you need to project based on multiple fields in the array's subdocuments.
See :ref:`array-field-limitation` for an example.
2 changes: 1 addition & 1 deletion source/includes/ref-toc-operator-projection.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: "Projects the first element in an array that matches the query cond
---
name: ":projection:`$elemMatch`"
file: /reference/operator/projection/elemMatch
description: "Projects only the first element from an array that matches the specified :projection:`$elemMatch` condition."
description: "Projects the first element in an array that matches the specified :projection:`$elemMatch` condition."
---
name: ":projection:`$meta`"
file: /reference/operator/projection/meta
Expand Down
8 changes: 8 additions & 0 deletions source/reference/method/cursor.sort.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ Or use :method:`~cursor.sort()` in conjunction with

db.stocks.find().sort( { ticker: 1, date: -1 } ).limit(100)

.. _sort-with-projection:

Interaction with :term:`Projection`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When a set of results are both sorted and projected, the MongoDB query engine
will always apply the sorting **first**.

Examples
--------

Expand Down
308 changes: 159 additions & 149 deletions source/reference/operator/projection/elemMatch.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,161 +6,171 @@ $elemMatch (projection)

.. default-domain:: mongodb

.. projection:: $elemMatch

.. versionadded:: 2.2

The :projection:`$elemMatch` projection operator limits the contents
of an array field that is included in the query results to contain
only the array element that matches the :projection:`$elemMatch`
condition.

.. note::

- The elements of the array are documents.

- If multiple elements match the :projection:`$elemMatch`
condition, the operator returns the **first** matching element
in the array.

- The :projection:`$elemMatch` projection operator is similar to
the positional :projection:`$` projection operator.

The examples on the :projection:`$elemMatch` projection operator
assumes a collection ``school`` with the following documents:

.. code-block:: javascript

{
_id: 1,
zipcode: "63109",
students: [
{ name: "john", school: 102, age: 10 },
{ name: "jess", school: 102, age: 11 },
{ name: "jeff", school: 108, age: 15 }
]
}
{
_id: 2,
zipcode: "63110",
students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
]
}

{
_id: 3,
zipcode: "63109",
students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
]
}

{
_id: 4,
zipcode: "63109",
students: [
{ name: "barney", school: 102, age: 7 },
]
}

.. example::

The following :method:`~db.collection.find()` operation
queries for all documents where the value of the ``zipcode``
field is ``63109``. The :projection:`$elemMatch` projection
returns only the **first** matching element of the ``students``
array where the ``school`` field has a value of ``102``:
Definition
----------

.. code-block:: javascript

db.schools.find( { zipcode: "63109" },
{ students: { $elemMatch: { school: 102 } } } )

The operation returns the following documents:

.. code-block:: javascript

{ "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 } ] }
{ "_id" : 3 }
{ "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }

- For the document with ``_id`` equal to ``1``, the ``students``
array contains multiple elements with the ``school`` field
equal to ``102``. However, the :projection:`$elemMatch`
projection returns only the first matching element from the
array.

- The document with ``_id`` equal to ``3`` does not contain the
``students`` field in the result since no element in its
``students`` array matched the :projection:`$elemMatch`
condition.

The :projection:`$elemMatch` projection can specify criteria on multiple
fields:

.. example::

The following :method:`~db.collection.find()` operation
queries for all documents where the value of the ``zipcode``
field is ``63109``. The projection includes the **first**
matching element of the ``students`` array where the ``school``
field has a value of ``102`` **and** the ``age`` field is greater
than ``10``:

.. code-block:: javascript

db.schools.find( { zipcode: "63109" },
{ students: { $elemMatch: { school: 102, age: { $gt: 10} } } } )

The operation returns the three documents that have ``zipcode`` equal to ``63109``:

.. code-block:: javascript

{ "_id" : 1, "students" : [ { "name" : "jess", "school" : 102, "age" : 11 } ] }
{ "_id" : 3 }
{ "_id" : 4 }

Documents with ``_id`` equal to ``3`` and ``_id`` equal to ``4``
do not contain the ``students`` field since no element matched
the :projection:`$elemMatch` criteria.

When the :method:`~db.collection.find()` method includes a
:method:`~cursor.sort()`, the :method:`~db.collection.find()` method
applies the :method:`~cursor.sort()` to order the matching documents
**before** it applies the projection.

If an array field contains multiple documents with the same field
name and the :method:`~db.collection.find()` method includes a
:method:`~cursor.sort()` on that repeating field, the returned
documents may not reflect the sort order because the
:method:`~cursor.sort()` was applied to the elements of the array
before the :projection:`$elemMatch` projection.

.. example::

The following query includes a :method:`~cursor.sort()` to order
by descending ``students.age`` field:
.. projection:: $elemMatch

.. code-block:: javascript
.. versionadded:: 2.2

The :projection:`$elemMatch` operator limits the contents of an
``<array>`` field from the query results to contain only the **first**
element matching the :projection:`$elemMatch` condition.

Usage Considerations
--------------------

.. include:: /includes/fact-positional-projection-vs-elemmatch.rst

Examples
--------

The examples on the :projection:`$elemMatch` projection operator
assumes a collection ``school`` with the following documents:

.. code-block:: javascript

{
_id: 1,
zipcode: "63109",
students: [
{ name: "john", school: 102, age: 10 },
{ name: "jess", school: 102, age: 11 },
{ name: "jeff", school: 108, age: 15 }
]
}
{
_id: 2,
zipcode: "63110",
students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
]
}
{
_id: 3,
zipcode: "63109",
students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
]
}
{
_id: 4,
zipcode: "63109",
students: [
{ name: "barney", school: 102, age: 7 },
{ name: "ruth", school: 102, age: 16 },
]
}

Zipcode Search
~~~~~~~~~~~~~~

The following :method:`~db.collection.find()` operation
queries for all documents where the value of the ``zipcode``
field is ``63109``. The :projection:`$elemMatch` projection
returns only the **first** matching element of the ``students``
array where the ``school`` field has a value of ``102``:

.. code-block:: javascript

db.schools.find( { zipcode: "63109" },
{ students: { $elemMatch: { school: 102 } } } )

The operation returns the following documents:

.. code-block:: javascript

{ "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 } ] }
{ "_id" : 3 }
{ "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }

- For the document with ``_id`` equal to ``1``, the ``students``
array contains multiple elements with the ``school`` field
equal to ``102``. However, the :projection:`$elemMatch`
projection returns only the first matching element from the
array.

- The document with ``_id`` equal to ``3`` does not contain the
``students`` field in the result since no element in its
``students`` array matched the :projection:`$elemMatch`
condition.

:projection:`$elemMatch` with Multiple Fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The :projection:`$elemMatch` projection can specify criteria on multiple
fields:

The following :method:`~db.collection.find()` operation
queries for all documents where the value of the ``zipcode``
field is ``63109``. The projection includes the **first**
matching element of the ``students`` array where the ``school``
field has a value of ``102`` **and** the ``age`` field is greater
than ``10``:

.. code-block:: javascript

db.schools.find( { zipcode: "63109" },
{ students: { $elemMatch: { school: 102, age: { $gt: 10} } } } )

The operation returns the three documents that have ``zipcode`` equal to ``63109``:

.. code-block:: javascript

{ "_id" : 1, "students" : [ { "name" : "jess", "school" : 102, "age" : 11 } ] }
{ "_id" : 3 }
{ "_id" : 4, "students" : [ { "name" : "ruth", "school" : 102, "age" : 16 } ] }

Documents with ``_id`` equal to ``3`` and ``_id`` equal to ``4``
do not contain the ``students`` field since no array element matched
the :projection:`$elemMatch` criteria.

:projection:`$elemMatch` with :method:`~cursor.sort()`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When the :method:`~db.collection.find()` method includes a
:method:`~cursor.sort()`, the :method:`~db.collection.find()` method
applies the :method:`~cursor.sort()` to order the matching documents
**before** it applies the projection. This is a general rule when sorting
and projecting, and is discussed in :ref:`sort-with-projection`.

If an array field contains multiple documents with the same field
name and the :method:`~db.collection.find()` method includes a
:method:`~cursor.sort()` on that repeating field, the returned
documents may not reflect the sort order because the
:method:`~cursor.sort()` was applied to the elements of the array
before the :projection:`$elemMatch` projection.

An array's sorting value is taken from either its "minimum" or "maximum" value,
depending on which way the sorting goes. The way that :method:`~cursor.sort()`
sorts documents containing arrays is described in :ref:`sort-asc-desc`.

The following query includes a :method:`~cursor.sort()` to order
by descending ``students.age`` field:

.. code-block:: javascript

db.schools.find(
{ zipcode: "63109" },
{ students: { $elemMatch: { school: 102 } } }
).sort( { "students.age": -1 } )

db.schools.find(
{ zipcode: 63109 },
{ students: { $elemMatch: { school: 102 } } }
).sort( { "students.age": -1 } )
The operation applies the :method:`~cursor.sort()` to order the
documents that have the field ``zipcode`` equal to ``63109`` and
then applies the projection. The operation returns the three
documents in the following order:

The operation applies the :method:`~cursor.sort()` to order the
documents that have the field ``zipcode`` equal to ``63109`` and
then applies the projection. The operation returns the three
documents in the following order:
.. code-block:: javascript

.. code-block:: javascript
{ "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }
{ "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 } ] }
{ "_id" : 3 }

{ "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 } ] }
{ "_id" : 3 }
{ "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }
Even though the sort is descending, the younger student is listed first. This
is because the sort occured before the older students in Barney's document were
projected out.

.. seealso::

Expand Down
Loading