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

Работа с массивами в документах

junior~35 мин

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

В прошлом уроке операторы меняли отдельные поля: числа, строки, вложенные документы. Но у книг магазина есть и массивы: метки в tags и отзывы в reviews. Положить в массив новый элемент, убрать лишний, поправить оценку в одном отзыве из трёх: для этого у MongoDB свой набор операторов. Примеры идут по порядку на учебной базе магазина.

Список покупок на холодильнике

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

С массивами в документе так же. В SQL метки книги лежали бы в отдельной таблице, и каждую правку мы делали бы INSERT или DELETE в ней. В MongoDB массив живёт внутри документа, и его правят прямо там.

$push: дописать в конец

$push добавляет элемент в конец массива. Отметим «Собачье сердце» как бестселлер. Для наглядности выполним команду дважды:

db.books.updateOne({ _id: 2 }, { $push: { tags: 'бестселлер' } })
db.books.updateOne({ _id: 2 }, { $push: { tags: 'бестселлер' } })
db.books.findOne({ _id: 2 }, { title: 1, tags: 1 })
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{
  _id: 2,
  title: 'Собачье сердце',
  tags: [ 'классика', 'сатира', 'бестселлер', 'бестселлер' ]
}

$push не проверяет, есть ли такой элемент. Метка теперь стоит дважды.

$pull: убрать по значению

$pull удаляет из массива все элементы, равные значению. Обе копии «бестселлера» уйдут разом:

db.books.updateOne({ _id: 2 }, { $pull: { tags: 'бестселлер' } })
db.books.findOne({ _id: 2 }, { tags: 1 })
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{ _id: 2, tags: [ 'классика', 'сатира' ] }

Вместо значения $pull принимает и условие. Например, { $pull: { reviews: { rating: { $lt: 3 } } } } удалит отзывы с оценкой ниже тройки. Это пригодится в конце урока.

$addToSet: добавить, если ещё нет

Для меток повторы не нужны. $addToSet добавляет элемент, только если его ещё нет в массиве:

db.books.updateOne({ _id: 2 }, { $addToSet: { tags: 'классика' } })
db.books.updateOne({ _id: 2 }, { $addToSet: { tags: { $each: ['классика', 'Москва'] } } })
db.books.findOne({ _id: 2 }, { tags: 1 })
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 0,
  upsertedCount: 0
}
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{ _id: 2, tags: [ 'классика', 'сатира', 'Москва' ] }

«Классика» уже была, первая команда ничего не изменила. Модификатор $each позволяет передать сразу несколько элементов: добавилась только «Москва». Тот же $each работает и с $push.

$pop: снять с края

$pop удаляет один элемент с края массива: 1 последний, -1 первый. У «Мастера и Маргариты» метки ['классика', 'мистика', 'Москва']:

db.books.updateOne({ _id: 1 }, { $pop: { tags: 1 } })
db.books.updateOne({ _id: 1 }, { $pop: { tags: -1 } })
db.books.findOne({ _id: 1 }, { tags: 1 })
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{ _id: 1, tags: [ 'мистика' ] }

Вместе $push и $pop превращают массив в стек или очередь, но для настоящих очередей задач лучше подходят другие инструменты, например Redis.

урок из курса

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

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