MongoDB Academy · Урок

Позиционные и фильтруемые позиционные обновления

Вы будете обновлять найденные элементы массива на месте с помощью позиционного оператора $ и фильтруемого позиционного оператора $[identifier].

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

«Позиционные и фильтруемые позиционные обновления» — бесплатный урок MongoDB Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения MongoDB Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс MongoDB Academy содержит 4 уроков всего.

Обновление элементов внутри массивов

Хотя $push и $pull добавляют и удаляют элементы массива, иногда требуется изменить существующий элемент на месте — изменить поле внутри уже находящегося в массиве вложенного документа. MongoDB предоставляет для этого два позиционных оператора: позиционный оператор $ для обновления первого подходящего элемента и позиционный оператор с фильтром $[identifier] для обновления всех подходящих элементов.

Позиционный оператор $

Позиционный оператор $ служит заполнителем для индекса первого элемента массива, соответствующего фильтру запроса. Используйте его в пути к полю обновления, например 'arrayField.$.subField', и MongoDB заменит $ индексом найденного элемента. Фильтр запроса должен включать условие для поля массива, чтобы MongoDB знала, какой элемент нужно изменить.

// Update the grade of the first matching scores element
db.students.updateOne(
  {
    _id: studentId,
    'scores.subject': 'math'  // match condition identifies which element
  },
  {
    $set: { 'scores.$.grade': 'A+' }  // $ = index of matched element
  }
);
// Only the FIRST element where subject='math' is updated

Ограничения оператора $

У позиционного оператора $ есть два важных ограничения: он обновляет только первый подходящий элемент, а не все подходящие элементы, и его нельзя безопасно использовать с updateMany() для обновления определённых элементов массивов в нескольких документах (он обновит первое совпадение в каждом документе). Чтобы обновить все подходящие элементы или несколько документов, необходим позиционный оператор с фильтром.

// Document: { scores: [{ subject: 'math', pass: false }, { subject: 'math', pass: false }] }

// $ only updates the FIRST math element
db.students.updateOne(
  { _id: id, 'scores.subject': 'math' },
  { $set: { 'scores.$.pass': true } }
);
// Result: [{ subject: 'math', pass: TRUE }, { subject: 'math', pass: FALSE }]
// Second element unchanged!

Позиционный оператор $[] для всех элементов

Оператор $[] (позиционный оператор для всех элементов) обновляет все элементы массива без условия фильтрации. Он служит заполнителем для каждого элемента массива. Используйте его, когда нужно применить одно и то же обновление ко всем элементам поля-массива — например, сбросить все флаги в массиве состояний или увеличить каждую оценку на бонусное значение.

// Give every score a +5 bonus
db.students.updateOne(
  { _id: studentId },
  { $inc: { 'scores.$[].value': 5 } }  // applies to ALL elements
);

// Set pass=true for every element in the array
db.students.updateMany(
  {},
  { $set: { 'scores.$[].reviewed': true } }  // updates across all documents
);

Позиционный оператор с фильтром $[identifier]

Фильтрованный позиционный оператор $[identifier] обновляет все элементы массива, соответствующие условию, заданному в параметре arrayFilters. identifier — это выбранное Вами имя-заполнитель (например, elem или score), а параметр arrayFilters определяет, на какие элементы должен указывать этот заполнитель. Это позволяет точно управлять тем, какие элементы будут обновлены.

// Update ALL math scores (not just the first)
db.students.updateOne(
  { _id: studentId },
  {
    $set: { 'scores.$[elem].grade': 'A' }  // 'elem' is the identifier
  },
  {
    arrayFilters: [{ 'elem.subject': 'math' }]  // filter for 'elem'
  }
);
// All elements where subject='math' get grade='A'

Сложные условия arrayFilters

Условия arrayFilters поддерживают все стандартные операторы запросов MongoDB: $gt, $lt, $in, $and и другие. Для обновления вложенных массивов можно задать несколько идентификаторов, используя в массиве несколько объектов фильтра. Каждый идентификатор должен быть уникальным буквенно-цифровым именем, начинающимся со строчной буквы.

// Fail all scores below 60 for a student
db.students.updateOne(
  { _id: studentId },
  { $set: { 'scores.$[lowScore].pass': false } },
  { arrayFilters: [{ 'lowScore.value': { $lt: 60 } }] }
);

// Update scores that are math AND below 70
db.students.updateOne(
  { _id: studentId },
  { $set: { 'scores.$[elem].needsHelp': true } },
  {
    arrayFilters: [{
      'elem.subject': 'math',
      'elem.value': { $lt: 70 }
    }]
  }
);

Фильтрованный позиционный оператор с updateMany

В отличие от оператора $, $[identifier] безопасно работает с updateMany(), применяя отфильтрованные обновления массивов ко всем документам коллекции. Это делает его особенно полезным для массовых операций: например, для изменения цен в определённых категориях товаров, вложенных в позиции заказов, или для применения скидки к некоторым счетам всех пользователей.

// Apply 10% discount to all 'sale' items across ALL orders
db.orders.updateMany(
  {},  // all documents
  { $mul: { 'lineItems.$[item].price': 0.9 } },
  { arrayFilters: [{ 'item.category': 'sale' }] }
);

// Mark all pending tasks as 'overdue' if dueDate has passed
db.projects.updateMany(
  {},
  { $set: { 'tasks.$[t].status': 'overdue' } },
  { arrayFilters: [{ 't.status': 'pending', 't.dueDate': { $lt: new Date() } }] }
);

Обновление вложенных массивов с несколькими идентификаторами

Для массивов, вложенных в другие массивы, можно использовать несколько идентификаторов в arrayFilters, чтобы обращаться к элементам на разных уровнях вложенности. Это продвинутый приём: каждый идентификатор применяется к своему уровню вложенности. Помните, что обновления глубоко вложенных массивов сложны и могут указывать на схему, которую стоит перестроить для большей ясности.

// Document: { courses: [{ name: 'Math', lessons: [{ id: 1, done: false }] }] }

// Mark a specific lesson as done inside a specific course
db.students.updateOne(
  { _id: studentId },
  { $set: { 'courses.$[course].lessons.$[lesson].done': true } },
  {
    arrayFilters: [
      { 'course.name': 'Math' },     // outer array filter
      { 'lesson.id': 1 }             // inner array filter
    ]
  }
);

Обновление по индексу с точечной нотацией

Если Вам известен точный числовой индекс элемента, который нужно обновить, можно напрямую использовать точечную нотацию: 'array.2.field' обновляет третий элемент (индексация начинается с 0). Это самый прямой подход, когда позиция известна во время записи. Его часто применяют для массивов с фиксированными позициями, где каждый индекс имеет определённое значение.

// Update the third element (index 2) directly
db.results.updateOne(
  { _id: testId },
  { $set: { 'answers.2.correct': true } }  // update answer at index 2
);

// Set a specific round's score in a game
db.games.updateOne(
  { _id: gameId },
  { $set: { 'rounds.0.score': 150 } }  // update round 1 (index 0)
);

Сочетание позиционных операторов с другими обновлениями

Позиционные операторы можно сочетать с любым оператором обновления — $set, $inc, $unset, $push — чтобы выполнять сложные изменения элементов массива на месте в рамках одной атомарной операции. Например, можно увеличить вложенное поле подходящего элемента массива и одновременно обновить временную метку документа.

// Increment attempt count for a specific question in a quiz
db.quizzes.updateOne(
  { _id: quizId, 'questions.id': 'q1' },
  {
    $inc: { 'questions.$.attemptCount': 1 },
    $set: { 'questions.$.lastAttempted': new Date(), updatedAt: new Date() }
  }
);
// All changes are atomic

Когда стоит изменить схему, а когда использовать позиционные операторы

Позиционные обновления очень мощны, однако сложные вложенные позиционные конструкции могут указывать на необходимость пересмотреть схему. Подумайте о перестройке, если Вам часто приходится запрашивать и обновлять элементы из нескольких вложенных массивов; если Вы пишете длинные пути в точечной нотации; или если логика приложения должна во время выполнения определять точные индексы элементов. Во многих случаях проще и удобнее в сопровождении перенести вложенные данные в отдельную коллекцию и использовать ссылки.

// Schema smells that suggest restructuring:
// 'courses.$[c].lessons.$[l].quizzes.$[q].answers.$.correct'
//   -> 5 levels of nesting is too complex

// Alternative: separate collections with references
// answers collection: { _id, quizId, lessonId, questionIndex, correct }

Быстрая проверка

Проверьте, насколько хорошо Вы понимаете операторы позиционного и фильтрованного позиционного обновления.

Итоги урока

В этом уроке Вы узнали: $ обновляет первый элемент массива, найденный по фильтру запроса, $[] безусловно обновляет все элементы массива, а $[identifier] вместе с arrayFilters обновляет все элементы, соответствующие условию. Далее мы рассмотрим конвейер агрегации — мощный механизм MongoDB для преобразования данных на стороне сервера.

Можно начать бесплатно

Изучай JavaScript с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
30
Уроки
120

Часто задаваемые вопросы

Урок «Позиционные и фильтруемые позиционные обновления» бесплатный?

Да — полный текст урока «Позиционные и фильтруемые позиционные обновления» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс MongoDB Academy, подпишись на CoddyKit PRO. Курс MongoDB Academy содержит 4 уроков всего.

Чему я научусь в уроке «Позиционные и фильтруемые позиционные обновления»?

Вы будете обновлять найденные элементы массива на месте с помощью позиционного оператора $ и фильтруемого позиционного оператора $[identifier]. Ты практикуешь MongoDB Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать MongoDB Academy?

Предыдущий опыт не требуется. MongoDB Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Позиционные и фильтруемые позиционные обновления»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке MongoDB Academy?

Да. Каждый урок MongoDB Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

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

  1. Запросы к массивам: $all, $size и сопоставление элементов
  2. $elemMatch: сопоставление вложенных документов в массивах
  3. Обновление массивов: $push, $pull, $pop, $addToSet
  4. Позиционные и фильтруемые позиционные обновления
← Назад к MongoDB Academy