Раздел 36 · MongoDB с нуля

Операторы обновления полей

junior~25 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлом уроке мы меняли документы одним оператором $set. Но склад магазина живёт не «поставь значение», а «продали две, привезли пять, скинули десять процентов». Сегодня операторы, которые меняют поле относительно его текущего значения, и пара служебных. Примеры идут по порядку на учебной базе магазина: каждый следующий видит изменения предыдущих.

Кнопки на кассе

У кассового аппарата нет кнопки «напиши в чеке 1290». Есть кнопки «плюс один товар», «скидка 10%», «отменить позицию». Кассир говорит, что сделать с суммой, а аппарат сам считает новую.

Операторы обновления работают так же. Ты не вычисляешь новое значение в приложении, а говоришь серверу действие. В одном документе обновления можно указать несколько операторов и несколько полей в каждом.

$set и $unset

$set мы уже знаем. Он умеет и вложенные поля через точку, и сразу несколько полей:

db.books.updateOne(
  { _id: 8 },
  { $set: { price: 2290, 'author.country': 'Канада', stock: 3 } }
)
db.books.findOne({ _id: 8 }, { title: 1, price: 1, author: 1, stock: 1 })
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{
  _id: 8,
  title: 'Совершенный код',
  price: 2290,
  author: { _id: 5, name: 'Макконнелл', country: 'Канада' },
  stock: 3
}

'author.country' поменял одно поле внутри вложенного документа, а name и _id автора остались. Если написать $set: { author: { country: 'Канада' } }, вложенный документ заменится целиком и имя пропадёт.

$unset удаляет поле. Значение после имени поля роли не играет, обычно пишут пустую строку:

db.books.updateOne({ _id: 8 }, { $unset: { stock: '' } })
db.books.findOne({ _id: 8 }, { title: 1, stock: 1 })
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{ _id: 8, title: 'Совершенный код' }

В таблице PostgreSQL удалить значение можно только записав NULL, колонка остаётся у всех строк. В MongoDB поля больше нет в этом документе.

$inc: прибавить

Заведём остаток на складе у всех книг и продадим две «Мастера и Маргариты»:

db.books.updateMany({}, { $set: { stock: 5 } })
db.books.updateOne({ _id: 1 }, { $inc: { stock: -2, sold: 2 } })
db.books.findOne({ _id: 1 }, { title: 1, stock: 1, sold: 1 })
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 9,
  modifiedCount: 9,
  upsertedCount: 0
}
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{ _id: 1, title: 'Мастер и Маргарита', stock: 3, sold: 2 }

$inc прибавляет число к полю, отрицательное число вычитает. Поля sold не было, и $inc создал его со значением 2.

Почему не прочитать остаток, вычесть в коде и записать через $set? Потому что двое покупателей могут сделать это одновременно. Оба прочитают 5, оба запишут 4, и одна продажа потеряется. $inc выполняется на сервере как одно атомарное действие над документом: два одновременных $inc дадут ровно 3. Про атомарность одного документа будет отдельный урок в кластере о транзакциях.

$inc работает только с числами. Строку увеличить нельзя:

db.books.updateOne({ _id: 1 }, { $inc: { title: 1 } })
Uncaught MongoServerError: Plan executor error during update :: caused by :: Cannot apply $inc to a value of non-numeric type. {_id: 1} has the field 'title' of non-numeric type string

урок из курса

Урок из курса «MongoDB с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.