Проекции массивов с $ и $elemMatch
Вы вернёте только первый совпадающий элемент массива или отфильтрованный подмассив с помощью проекций $ и $elemMatch.
«Проекции массивов с $ и $elemMatch» — бесплатный урок MongoDB Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения MongoDB Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс MongoDB Academy содержит 4 уроков всего.
Проблема: возврат только одного элемента массива
Иногда запрос находит документ по элементу массива — например, заказ, содержащий определённый товар, — но нужно вернуть только подходящий элемент массива, а не весь массив. Стандартная проекция с включением возвращает все элементы массива. MongoDB предоставляет два специальных оператора проекции массивов для решения этой задачи: позиционный оператор $ и $elemMatch.
Позиционный оператор $
Позиционный оператор проекции $ возвращает первый элемент массива, соответствующий условию запроса. В проекции он размещается там, где обычно указывается имя поля массива. Условие, совпавшее с фильтром, автоматически используется для определения элемента, который нужно спроецировать. В одной проекции может присутствовать только один $.
// Find the order and return only the matching line item
db.orders.findOne(
{ 'items.productId': ObjectId('p1') },
{ projection: { 'items.$': 1 } }
);
// Result: { _id: ..., items: [{ productId: ObjectId('p1'), qty: 2, price: 9.99 }] }
// Only the FIRST matching element is returnedКак $ сопоставляется с условием фильтра
Оператор $ использует условие фильтра для поля массива из запроса, чтобы определить возвращаемый элемент. Фильтр запроса должен содержать условие для того же поля массива, которое указано в проекции. Если фильтр соответствует нескольким элементам, возвращается только первое совпадение (в порядке элементов массива). Это важное ограничение, о котором следует помнить.
// Filter on items.qty, project only that matching item
db.orders.find(
{ 'items.qty': { $gt: 1 } },
{ projection: { 'items.$': 1, total: 1 } }
);
// Returns the first item in the items array where qty > 1
// If multiple items have qty > 1, only the first one is projectedОграничения оператора $
Позиционный оператор $ имеет два ключевых ограничения: (1) он возвращает только первый подходящий элемент, даже если фильтру соответствуют несколько элементов; (2) поле массива должно присутствовать в фильтре запроса (нельзя проецировать с помощью $ поле, которого не было в условии фильтра). Для сопоставления по нескольким условиям или с несколькими элементами используйте $elemMatch в проекции.
Оператор проекции $elemMatch
$elemMatch в проекции возвращает только первый элемент массива, соответствующий указанным условиям, подобно оператору $, но с одним важным отличием: условия фильтра указываются непосредственно в проекции, а не в фильтре запроса. Это позволяет спроецировать подходящий элемент массива, даже если основной фильтр запроса использует другие критерии.
// Find all orders, but from the items array return only the item with qty > 1
db.orders.find(
{ status: 'shipped' }, // main filter on a different field
{
projection: {
total: 1,
items: { $elemMatch: { qty: { $gt: 1 } } } // array filter in projection
}
}
);
// items array is present only if an element matches; absent if none match$ и $elemMatch: ключевое различие
Основное различие заключается в следующем:
$в проекции: условие совпадения берётся из фильтра запроса. Поле массива должно присутствовать в фильтре.$elemMatchв проекции: Вы записываете отдельное условие непосредственно в самой проекции. Основной фильтр может применяться к любому полю.
$, если условие фильтра уже относится к полю массива; используйте проекцию $elemMatch, если нужно другое условие или фильтр относится к другому полю.$elemMatch с несколькими условиями
$elemMatch в проекции может применять несколько условий к поддокументу внутри массива — все условия должны выполняться для одного и того же элемента массива. Это особенно важно для поддокументов: без $elemMatch MongoDB проверяет условия в разных элементах и может возвращать ложные совпадения.
// From orders, return only items where qty > 1 AND price < 20
db.orders.find(
{ status: 'delivered' },
{
projection: {
items: {
$elemMatch: {
qty: { $gt: 1 },
price: { $lt: 20 }
}
}
}
}
);
// Both conditions must match the SAME array elementОтсутствующее поле массива, если совпадений нет
При использовании $elemMatch в проекции, если ни один элемент массива не удовлетворяет условию, поле массива полностью отсутствует в результирующем документе (оно не представлено пустым массивом). Поэтому код приложения должен учитывать, что спроецированное поле массива может иметь значение undefined. Всегда проверяйте существование поля перед обращением к его элементам.
const order = await db.collection('orders').findOne(
{ status: 'shipped' },
{ projection: { items: { $elemMatch: { qty: { $gt: 100 } } } } }
);
// Safe access — items may be absent if no element matched
const matchedItem = order.items ? order.items[0] : null;
console.log('Matched item:', matchedItem);Сочетание $elemMatch с другими проекциями
Можно сочетать проекцию массива с $elemMatch и обычные проекции полей в одном запросе. Проецируйте скалярные поля с помощью обычного синтаксиса включения и применяйте $elemMatch только к полю массива. Помните о правиле несовместимости: все поля, не являющиеся массивами, должны использовать один и тот же режим — включение или исключение.
// Project total and status (inclusion) + first matching item
db.orders.findOne(
{ customerId: ObjectId('c1') },
{
projection: {
total: 1,
status: 1,
_id: 0,
items: { $elemMatch: { qty: { $gt: 0 } } }
}
}
);$elemMatch в фильтре запроса и в проекции
Будьте внимательны и не путайте $elemMatch в фильтре запроса (он выбирает документы) с $elemMatch в проекции (он выбирает возвращаемый элемент). Форма в фильтре определяет, какие документы будут возвращены; форма в проекции определяет, какие элементы массива появятся в этих документах. Их часто используют вместе, но задачи у них разные.
// $elemMatch in FILTER: find orders containing a specific item
db.orders.find({
items: { $elemMatch: { productId: ObjectId('p1'), qty: { $gt: 1 } } }
});
// $elemMatch in PROJECTION: from all shipped orders, return only that matching item
db.orders.find(
{ status: 'shipped' },
{ projection: { items: { $elemMatch: { productId: ObjectId('p1'), qty: { $gt: 1 } } } } }
);Практический пример: оценки пользователей
Документ таблицы лидеров игры хранит все оценки игрока во встроенном массиве. При отображении оценки игрока для конкретной игры нужно вернуть только элемент с оценкой этой игры, а не всю историю оценок. $elemMatch в проекции выбирает именно этот элемент, сохраняя небольшой размер ответа, даже если у игрока записаны оценки для тысяч игр.
db.players.find(
{ username: 'gamer42' },
{
projection: {
username: 1,
scores: { $elemMatch: { gameId: 'chess_blitz' } },
_id: 0
}
}
);
// Result: { username: 'gamer42', scores: [{ gameId: 'chess_blitz', score: 1540 }] }Быстрая проверка
Проверьте, насколько хорошо Вы усвоили концепции MongoDB и баз данных NoSQL из этого урока.
Итоги урока
В этом уроке Вы узнали: позиционный оператор $ возвращает первый элемент, соответствующий условию фильтра запроса, $elemMatch в проекции позволяет задавать условия совпадения независимо от фильтра запроса, а если ни один элемент не соответствует $elemMatch, поле отсутствует в результате. Далее мы рассмотрим рекомендации по использованию проекций для ответов API.
Изучай JavaScript с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 30
- Уроки
- 120
Часто задаваемые вопросы
Урок «Проекции массивов с $ и $elemMatch» бесплатный?
Да — полный текст урока «Проекции массивов с $ и $elemMatch» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс MongoDB Academy, подпишись на CoddyKit PRO. Курс MongoDB Academy содержит 4 уроков всего.
Чему я научусь в уроке «Проекции массивов с $ и $elemMatch»?
Вы вернёте только первый совпадающий элемент массива или отфильтрованный подмассив с помощью проекций $ и $elemMatch. Ты практикуешь MongoDB Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать MongoDB Academy?
Предыдущий опыт не требуется. MongoDB Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Проекции массивов с $ и $elemMatch»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке MongoDB Academy?
Да. Каждый урок MongoDB Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Проекции с включением и исключением
- Проецирование вложенных полей и полей массивов
- Проекции массивов с $ и $elemMatch
- Рекомендации по проекциям для ответов API