Skip to content

Commit 9446cf9

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

File tree

5 files changed

+229
-181
lines changed

5 files changed

+229
-181
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: 158 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -6,161 +6,173 @@ $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::
9+
Definition
10+
----------
7011

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``:
76-
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:
12+
.. projection:: $elemMatch
10313

104-
.. example::
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+
--------
10527

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} } } } )
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+
{
52+
_id: 3,
53+
zipcode: "63109",
54+
students: [
55+
{ name: "ajax", school: 100, age: 7 },
56+
{ name: "achilles", school: 100, age: 8 },
57+
]
58+
}
59+
60+
{
61+
_id: 4,
62+
zipcode: "63109",
63+
students: [
64+
{ name: "barney", school: 102, age: 7 },
65+
{ name: "ruth", school: 102, age: 16 },
66+
]
67+
}
68+
69+
Zipcode Search
70+
~~~~~~~~~~~~~~
71+
72+
The following :method:`~db.collection.find()` operation
73+
queries for all documents where the value of the ``zipcode``
74+
field is ``63109``. The :projection:`$elemMatch` projection
75+
returns only the **first** matching element of the ``students``
76+
array where the ``school`` field has a value of ``102``:
77+
78+
.. code-block:: javascript
79+
80+
db.schools.find( { zipcode: "63109" },
81+
{ students: { $elemMatch: { school: 102 } } } )
82+
83+
The operation returns the following documents:
84+
85+
.. code-block:: javascript
86+
87+
{ "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 } ] }
88+
{ "_id" : 3 }
89+
{ "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }
90+
91+
- For the document with ``_id`` equal to ``1``, the ``students``
92+
array contains multiple elements with the ``school`` field
93+
equal to ``102``. However, the :projection:`$elemMatch`
94+
projection returns only the first matching element from the
95+
array.
96+
97+
- The document with ``_id`` equal to ``3`` does not contain the
98+
``students`` field in the result since no element in its
99+
``students`` array matched the :projection:`$elemMatch`
100+
condition.
101+
102+
:projection:`$elemMatch` with Multiple Fields
103+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
104+
105+
The :projection:`$elemMatch` projection can specify criteria on multiple
106+
fields:
107+
108+
The following :method:`~db.collection.find()` operation
109+
queries for all documents where the value of the ``zipcode``
110+
field is ``63109``. The projection includes the **first**
111+
matching element of the ``students`` array where the ``school``
112+
field has a value of ``102`` **and** the ``age`` field is greater
113+
than ``10``:
114+
115+
.. code-block:: javascript
116+
117+
db.schools.find( { zipcode: "63109" },
118+
{ students: { $elemMatch: { school: 102, age: { $gt: 10} } } } )
119+
120+
The operation returns the three documents that have ``zipcode`` equal to ``63109``:
121+
122+
.. code-block:: javascript
123+
124+
{ "_id" : 1, "students" : [ { "name" : "jess", "school" : 102, "age" : 11 } ] }
125+
{ "_id" : 3 }
126+
{ "_id" : 4, "students" : [ { "name" : "ruth", "school" : 102, "age" : 16 } ] }
127+
128+
Documents with ``_id`` equal to ``3`` and ``_id`` equal to ``4``
129+
do not contain the ``students`` field since no array element matched
130+
the :projection:`$elemMatch` criteria.
131+
132+
:projection:`$elemMatch` with :method:`~cursor.sort()`
133+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
134+
135+
When the :method:`~db.collection.find()` method includes a
136+
:method:`~cursor.sort()`, the :method:`~db.collection.find()` method
137+
applies the :method:`~cursor.sort()` to order the matching documents
138+
**before** it applies the projection. This is a general rule when sorting
139+
and projecting, and is discussed in :ref:`sort-with-projection`.
140+
141+
If an array field contains multiple documents with the same field
142+
name and the :method:`~db.collection.find()` method includes a
143+
:method:`~cursor.sort()` on that repeating field, the returned
144+
documents may not reflect the sort order because the
145+
:method:`~cursor.sort()` was applied to the elements of the array
146+
before the :projection:`$elemMatch` projection.
147+
148+
An array's sorting value is taken from either its "minimum" or "maximum" value,
149+
depending on which way the sorting goes. The way that :method:`~cursor.sort()`
150+
sorts documents containing arrays is described in :ref:`sort-asc-desc`.
151+
152+
The following query includes a :method:`~cursor.sort()` to order
153+
by descending ``students.age`` field:
117154

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:
155+
.. code-block:: javascript
146156

147-
.. code-block:: javascript
157+
db.schools.find(
158+
{ zipcode: "63109" },
159+
{ students: { $elemMatch: { school: 102 } } }
160+
).sort( { "students.age": -1 } )
148161

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

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:
167+
.. code-block:: javascript
158168

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

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

165177
.. seealso::
166178

0 commit comments

Comments
 (0)