Skip to content

Commit ac39cf3

Browse files
committed
DOCS-2643: Clarify distinction between positional $ and projection $elemMatch.
1 parent eda4eda commit ac39cf3

File tree

5 files changed

+230
-184
lines changed

5 files changed

+230
-184
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Both the :projection:`$` operator and the :projection:`$elemMatch` operator project
2+
a subset of elements from an array based on a condition.
3+
4+
The :projection:`$` operator projects the array elements based on some condition
5+
from the query statement.
6+
7+
The :projection:`$elemMatch` projection operator takes an explicit condition
8+
argument. This allows you to project based on a condition not in the query, or
9+
if you need to project based on multiple fields in the array's subdocuments.
10+
See :ref:`array-field-limitation` for an example.

source/includes/ref-toc-operator-projection.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: "Projects the first element in an array that matches the query cond
44
---
55
name: ":projection:`$elemMatch`"
66
file: /reference/operator/projection/elemMatch
7-
description: "Projects only the first element from an array that matches the specified :projection:`$elemMatch` condition."
7+
description: "Projects the first element in an array that matches the specified :projection:`$elemMatch` condition."
88
---
99
name: ":projection:`$meta`"
1010
file: /reference/operator/projection/meta

source/reference/method/cursor.sort.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ Or use :method:`~cursor.sort()` in conjunction with
108108

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

111+
.. _sort-with-projection:
112+
113+
Interaction with :term:`Projection`
114+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
115+
116+
When a set of results are both sorted and projected, the MongoDB query engine
117+
will always apply the sorting **first**.
118+
111119
Examples
112120
--------
113121

source/reference/operator/projection/elemMatch.txt

Lines changed: 159 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -6,161 +6,171 @@ $elemMatch (projection)
66

77
.. default-domain:: mongodb
88

9-
.. projection:: $elemMatch
10-
11-
.. versionadded:: 2.2
12-
13-
The :projection:`$elemMatch` projection operator limits the contents
14-
of an array field that is included in the query results to contain
15-
only the array element that matches the :projection:`$elemMatch`
16-
condition.
17-
18-
.. note::
19-
20-
- The elements of the array are documents.
21-
22-
- If multiple elements match the :projection:`$elemMatch`
23-
condition, the operator returns the **first** matching element
24-
in the array.
25-
26-
- The :projection:`$elemMatch` projection operator is similar to
27-
the positional :projection:`$` projection operator.
28-
29-
The examples on the :projection:`$elemMatch` projection operator
30-
assumes a collection ``school`` with the following documents:
31-
32-
.. code-block:: javascript
33-
34-
{
35-
_id: 1,
36-
zipcode: "63109",
37-
students: [
38-
{ name: "john", school: 102, age: 10 },
39-
{ name: "jess", school: 102, age: 11 },
40-
{ name: "jeff", school: 108, age: 15 }
41-
]
42-
}
43-
{
44-
_id: 2,
45-
zipcode: "63110",
46-
students: [
47-
{ name: "ajax", school: 100, age: 7 },
48-
{ name: "achilles", school: 100, age: 8 },
49-
]
50-
}
51-
52-
{
53-
_id: 3,
54-
zipcode: "63109",
55-
students: [
56-
{ name: "ajax", school: 100, age: 7 },
57-
{ name: "achilles", school: 100, age: 8 },
58-
]
59-
}
60-
61-
{
62-
_id: 4,
63-
zipcode: "63109",
64-
students: [
65-
{ name: "barney", school: 102, age: 7 },
66-
]
67-
}
68-
69-
.. example::
70-
71-
The following :method:`~db.collection.find()` operation
72-
queries for all documents where the value of the ``zipcode``
73-
field is ``63109``. The :projection:`$elemMatch` projection
74-
returns only the **first** matching element of the ``students``
75-
array where the ``school`` field has a value of ``102``:
9+
Definition
10+
----------
7611

77-
.. code-block:: javascript
78-
79-
db.schools.find( { zipcode: "63109" },
80-
{ students: { $elemMatch: { school: 102 } } } )
81-
82-
The operation returns the following documents:
83-
84-
.. code-block:: javascript
85-
86-
{ "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 } ] }
87-
{ "_id" : 3 }
88-
{ "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }
89-
90-
- For the document with ``_id`` equal to ``1``, the ``students``
91-
array contains multiple elements with the ``school`` field
92-
equal to ``102``. However, the :projection:`$elemMatch`
93-
projection returns only the first matching element from the
94-
array.
95-
96-
- The document with ``_id`` equal to ``3`` does not contain the
97-
``students`` field in the result since no element in its
98-
``students`` array matched the :projection:`$elemMatch`
99-
condition.
100-
101-
The :projection:`$elemMatch` projection can specify criteria on multiple
102-
fields:
103-
104-
.. example::
105-
106-
The following :method:`~db.collection.find()` operation
107-
queries for all documents where the value of the ``zipcode``
108-
field is ``63109``. The projection includes the **first**
109-
matching element of the ``students`` array where the ``school``
110-
field has a value of ``102`` **and** the ``age`` field is greater
111-
than ``10``:
112-
113-
.. code-block:: javascript
114-
115-
db.schools.find( { zipcode: "63109" },
116-
{ students: { $elemMatch: { school: 102, age: { $gt: 10} } } } )
117-
118-
The operation returns the three documents that have ``zipcode`` equal to ``63109``:
119-
120-
.. code-block:: javascript
121-
122-
{ "_id" : 1, "students" : [ { "name" : "jess", "school" : 102, "age" : 11 } ] }
123-
{ "_id" : 3 }
124-
{ "_id" : 4 }
125-
126-
Documents with ``_id`` equal to ``3`` and ``_id`` equal to ``4``
127-
do not contain the ``students`` field since no element matched
128-
the :projection:`$elemMatch` criteria.
129-
130-
When the :method:`~db.collection.find()` method includes a
131-
:method:`~cursor.sort()`, the :method:`~db.collection.find()` method
132-
applies the :method:`~cursor.sort()` to order the matching documents
133-
**before** it applies the projection.
134-
135-
If an array field contains multiple documents with the same field
136-
name and the :method:`~db.collection.find()` method includes a
137-
:method:`~cursor.sort()` on that repeating field, the returned
138-
documents may not reflect the sort order because the
139-
:method:`~cursor.sort()` was applied to the elements of the array
140-
before the :projection:`$elemMatch` projection.
141-
142-
.. example::
143-
144-
The following query includes a :method:`~cursor.sort()` to order
145-
by descending ``students.age`` field:
12+
.. projection:: $elemMatch
14613

147-
.. code-block:: javascript
14+
.. versionadded:: 2.2
15+
16+
The :projection:`$elemMatch` operator limits the contents of an
17+
``<array>`` field from the query results to contain only the **first**
18+
element matching the :projection:`$elemMatch` condition.
19+
20+
Usage Considerations
21+
--------------------
22+
23+
.. include:: /includes/fact-positional-projection-vs-elemmatch.rst
24+
25+
Examples
26+
--------
27+
28+
The examples on the :projection:`$elemMatch` projection operator
29+
assumes a collection ``school`` with the following documents:
30+
31+
.. code-block:: javascript
32+
33+
{
34+
_id: 1,
35+
zipcode: "63109",
36+
students: [
37+
{ name: "john", school: 102, age: 10 },
38+
{ name: "jess", school: 102, age: 11 },
39+
{ name: "jeff", school: 108, age: 15 }
40+
]
41+
}
42+
{
43+
_id: 2,
44+
zipcode: "63110",
45+
students: [
46+
{ name: "ajax", school: 100, age: 7 },
47+
{ name: "achilles", school: 100, age: 8 },
48+
]
49+
}
50+
{
51+
_id: 3,
52+
zipcode: "63109",
53+
students: [
54+
{ name: "ajax", school: 100, age: 7 },
55+
{ name: "achilles", school: 100, age: 8 },
56+
]
57+
}
58+
{
59+
_id: 4,
60+
zipcode: "63109",
61+
students: [
62+
{ name: "barney", school: 102, age: 7 },
63+
{ name: "ruth", school: 102, age: 16 },
64+
]
65+
}
66+
67+
Zipcode Search
68+
~~~~~~~~~~~~~~
69+
70+
The following :method:`~db.collection.find()` operation
71+
queries for all documents where the value of the ``zipcode``
72+
field is ``63109``. The :projection:`$elemMatch` projection
73+
returns only the **first** matching element of the ``students``
74+
array where the ``school`` field has a value of ``102``:
75+
76+
.. code-block:: javascript
77+
78+
db.schools.find( { zipcode: "63109" },
79+
{ students: { $elemMatch: { school: 102 } } } )
80+
81+
The operation returns the following documents:
82+
83+
.. code-block:: javascript
84+
85+
{ "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 } ] }
86+
{ "_id" : 3 }
87+
{ "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }
88+
89+
- For the document with ``_id`` equal to ``1``, the ``students``
90+
array contains multiple elements with the ``school`` field
91+
equal to ``102``. However, the :projection:`$elemMatch`
92+
projection returns only the first matching element from the
93+
array.
94+
95+
- The document with ``_id`` equal to ``3`` does not contain the
96+
``students`` field in the result since no element in its
97+
``students`` array matched the :projection:`$elemMatch`
98+
condition.
99+
100+
:projection:`$elemMatch` with Multiple Fields
101+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
102+
103+
The :projection:`$elemMatch` projection can specify criteria on multiple
104+
fields:
105+
106+
The following :method:`~db.collection.find()` operation
107+
queries for all documents where the value of the ``zipcode``
108+
field is ``63109``. The projection includes the **first**
109+
matching element of the ``students`` array where the ``school``
110+
field has a value of ``102`` **and** the ``age`` field is greater
111+
than ``10``:
112+
113+
.. code-block:: javascript
114+
115+
db.schools.find( { zipcode: "63109" },
116+
{ students: { $elemMatch: { school: 102, age: { $gt: 10} } } } )
117+
118+
The operation returns the three documents that have ``zipcode`` equal to ``63109``:
119+
120+
.. code-block:: javascript
121+
122+
{ "_id" : 1, "students" : [ { "name" : "jess", "school" : 102, "age" : 11 } ] }
123+
{ "_id" : 3 }
124+
{ "_id" : 4, "students" : [ { "name" : "ruth", "school" : 102, "age" : 16 } ] }
125+
126+
Documents with ``_id`` equal to ``3`` and ``_id`` equal to ``4``
127+
do not contain the ``students`` field since no array element matched
128+
the :projection:`$elemMatch` criteria.
129+
130+
:projection:`$elemMatch` with :method:`~cursor.sort()`
131+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
132+
133+
When the :method:`~db.collection.find()` method includes a
134+
:method:`~cursor.sort()`, the :method:`~db.collection.find()` method
135+
applies the :method:`~cursor.sort()` to order the matching documents
136+
**before** it applies the projection. This is a general rule when sorting
137+
and projecting, and is discussed in :ref:`sort-with-projection`.
138+
139+
If an array field contains multiple documents with the same field
140+
name and the :method:`~db.collection.find()` method includes a
141+
:method:`~cursor.sort()` on that repeating field, the returned
142+
documents may not reflect the sort order because the
143+
:method:`~cursor.sort()` was applied to the elements of the array
144+
before the :projection:`$elemMatch` projection.
145+
146+
An array's sorting value is taken from either its "minimum" or "maximum" value,
147+
depending on which way the sorting goes. The way that :method:`~cursor.sort()`
148+
sorts documents containing arrays is described in :ref:`sort-asc-desc`.
149+
150+
The following query includes a :method:`~cursor.sort()` to order
151+
by descending ``students.age`` field:
152+
153+
.. code-block:: javascript
154+
155+
db.schools.find(
156+
{ zipcode: "63109" },
157+
{ students: { $elemMatch: { school: 102 } } }
158+
).sort( { "students.age": -1 } )
148159

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

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

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

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

165175
.. seealso::
166176

0 commit comments

Comments
 (0)