В прошлом уроке мы меняли документы одним оператором $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 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.