MongoDB Academy · Урок

Проекции массивов с $ и $elemMatch

Вы вернёте только первый совпадающий элемент массива или отфильтрованный подмассив с помощью проекций $ и $elemMatch.

Урок 3 из 413 шагов

«Проекции массивов с $ и $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 — локальная установка не требуется.

Все уроки этого курса

  1. Проекции с включением и исключением
  2. Проецирование вложенных полей и полей массивов
  3. Проекции массивов с $ и $elemMatch
  4. Рекомендации по проекциям для ответов API
← Назад к MongoDB Academy