Изучаем MongoDB: редактирование документов
Перевод: Paras — Learn MongoDB: Update Documents
Рассмотрев запросы к документам, пришло время научиться редактировать документы. Если вы знаете, как найти свои данные, вам будет проще их изменять. Итак, приступим!
Содержание :
- Методы редактирования в mongo
- Редактирование документов
- Операторы редактирования
- Редактирование массивов
- Другие операции с массивами
Методы редактирования в mongo
Методы, которые помогают нам изменять документы в mongodb:
- updateOne
- updateMany
В этом посте мы будем работать с коллекцией trainers
. Хватит покемонов !! Для начала сделаем простую структуру:
{ name: "Ash", age: 18, pokemons: [ { name: "pikachu", type: "electric", level: 16 }, { name: "charizard", type: "fire", level: 30 }, { name: "squirtle", type: "water", level: 12 } ], badges: ["boulder badge", "cascade badge"], exp: 200, currentTown: "Pallet Town" }
Далее мы будем добавлять данные и изменять структуру в соответствии с нашими потребностями.
Редактирование документов
Первый оператор, с которым вам следует познакомится для обновления документов, — это $set. Если мы попытаемся обновить документы без этого оператора, это приведет к ошибке или неожиданному поведению.
Методы обновления принимают первый параметр в качестве фильтра документов для определения документов, которые вы хотите обновить, и второй параметр значение, которое вы хотите применить к отфильтрованным документам. Есть третий параметр, но мы узнаем о нем позже в этом посте.
Базовый пример
# меняем имя тренера с Ash на Misty > db.trainers.updateOne( {name: "Ash"}, {$set: {name: "Misty"}} ) # задаем возраст всех тренеров 18 > db.trainers.updateMany({}, {$set: {age: 18}}) # задаем новое поле для всех тренеров > db.trainers.updateMany( {}, {$set: {hasPokemon: false}} )
Вы не знаете, но mongo достаточно умен, и если вы попытаетесь обновить поле с тем же значением, которое у него уже есть, оно проигнорирует эту команду
Операторы редактирования
Операторы обновления помогают нам обновлять документы разными способами.
Операторы:
- $inc : Увеличение или уменьшение числовых значений
- $unset : Удаляет поле из документа
- $rename : Переименовать поле
- $min : Используется чтобы обновить числовое поле с заданным значением, если оно меньше существующего значения
- $max : Используется чтобы обновить числовое поле с заданным значением, если оно больше, чем существующее значение
- $upsert : Если документ существует, обновляет его, иначе создает новый документ
- $mul : Используется чтобы выполнить умножение на числовых полях
Для выполнения умножения числовых полей существуют и другие операторы, которые также помогают вам работать с массивами, и мы обсудим их позже.
Итак, давайте посмотрим несколько примеров для вышеуказанных операторов и их использования.
Структура документа Trainers
{ name: "Ash", age: 18, pokemons: [ { name: "pikachu", type: "electric", level: 16 }, { name: "charizard", type: "fire", level: 30 }, { name: "squirtle", type: "water", level: 12 } ], badges: ["boulder badge", "cascade badge"], exp: 200, currentTown: "Pallet Town", bagItems: { pokeballs: 8, heals: 10 }, isFunny: true }
Пример:
- Увеличение и уменьшение числовых значений в документе
# увеличиваем опыт "Ash" на 50 > db.trainers.updateOne({name: "Ash"}, {$inc: {exp: 50}}) # уменьшаем количество pokeballs в мешке Ash на 2 > db.trainers.updateOne({name: "Ash"}, {$inc: {"bagItems.pokeballs": -2}})
- Обновление числовых значений в соответствии с текущим значением с помощью $min и $max
# обновляем количество heals в сумке Ash, если в ней heals меньше значения обновления # это будет обновляться, только если переданное значение меньше значения в db > db.trainers.updateOne({name: "Ash"}, {$min: {"bagItems.heals": 25}}) # обновляем exp до 300, только если новый exp. больше текущего exp > db.trainers.updateOne({name: "Brock"}, {$max: {exp: 300}})
- Выполняем умножения числового поля
# умножаем exp Ash(и сделаем его непобедимым !!) > db.trainers.updateOne({name: "Ash"}, {$mul: {exp: 100}})
- Давайте посмотрим на операции с полями и начнем с удаления поля из документа
# удаляем поле "isFunny" со всех документов > db.trainers.updateMany({}, {$unset: {isFunny: ""}}) # ПРИМЕЧАНИЕ 1. Значение, которое вы передаете в поле, может быть любым. Здесь важно имя поля # ПРИМЕЧАНИЕ 2. Вы также можете удалить вложенные поля в документах, например. "bagItems.heals"
- Переименование поля
# переименовываем поле "exp" в "experience" > db.trainers.updateMany({}, {$rename: {exp: "experience"}})
- Обновляем документ и, если его не существует, создаем его.
> db.trainers.updateOne( {name: "Paul"}, {$set: {age: 19, exp: 200}}, {upsert: true} )
Здесь вы видите третий аргумент, который принимает метод updateOne. Это похоже на параметры запроса, которые вы можете изменять. По умолчанию для upsert установлено значение false. Сделав upsert истинным, мы получили метод обновления и метод создания как в нашем обновлении.
Результат вышеуказанного запроса
{ name: "Paul", age: 19, exp: 200 }
Как видите, mongo не только создает документ с новыми полями, но также добавляет поле, которое мы передали как фильтр в первом аргументе. Круто, не правда ли.
Редактирование массивов
Обновление массивов немного отличается от обновления других полей в документе или вложенных документах. Но это не ракетостроение.
Начнем с некоторых операторов, которые могут помочь нам выполнять основные операции с массивами.
$push
; добавить элемент в массив в документе$pull
: удалить указанный элемент из массива в документе$pop
: удалить элемент из массива либо с конца, либо с начала$each
: помогает протолкнуть сразу несколько элементов вместо одного$addToSet
: обрабатывает массив как набор и добавляет значение, только если оно не существует
Пример:
# добавляем нового покемона к покемонам Ash > db.trainers.updateOne( {name: "Ash"}, {$push: { pokemons: { name: "bulbasaur", type: "grass", level: 17 }}} ) # добавляем боле чем одного покемона к покемонам Ash с $each > db.trainers.updateOne( {name: "Ash"}, { $push: { pokemons: { $each: [ {name: "butterfree", type: "bug", level: 20}, {name: "piggeotto", type: "flying", level: 14}, {name: "Krabby", type: "water", level: 13} ]} } } ) # удаляем badge из массива Ash > db.trainers.updateOne( {name: "Ash"}, {$pull: {badges: "boulder badge"}} ) # давай еще заберем покемона у Ash(в шутку) > db.trainers.updateOne( {name: "Ash"}, {$pull: {pokemons: {name: "krabby"}}} ) # вытащить покемона из Misty с конца > db.trainers.updateOne({name: "Misty"}, {$pop: {pokemons: 1}}) # вытащить покемона из Misty с самого начала > db.trainers.updateOne({name: "Misty"}, {$pop: {pokemons: -1}}) # мы дадим Brock badge , но мы должны убедиться, что у него нет двух значков одного типа. Для этого нам нужно рассматривать массив как набор set > db.trainers.updateOne( {name: "Brock"}, {$addToSet: {badges: "Thunder Badge"}} ) # попробуйте сделать этот запрос еще раз, и вы заметите, что он не добавит еще один значок Thunder к значкам Brock.
- Объедините $addToSet с $each, чтобы добавить сразу несколько уникальных значений
- Использование $pull удаляет все совпадающие элементы из массива, а не только одно совпадение.
Другие операции с массивами
Есть и другие операции, связанные с позициями в массиве. Может быть, вы хотите обновить четвертый элемент в массиве (по 3-му индексу) или хотите обновить все совпавшие элементы в массиве, а не только один. Таких ситуаций много.
Работа с индексом
# Повышает уровень первого покемона Ash на 1 > db.trainers.updateOne({name: "Ash"}, {$inc: {"pokemons.0.level": 1}})
Мы можем найти определенный элемент в массиве и обновить этот конкретный элемент, не зная его индекса.
$
: Вместо передачи индекса мы можем использовать этот позиционный оператор для ссылки на совпадающие результаты из нашего фильтра cм. пример ниже.
Запросить и обновить соответствующий элемент в массиве
# добавим поле health ко всем тренерам пикачу. > db.trainers.updateOne( {"pokemon.name": "pikachu"}, # соответствующий фильтр {$set: {"pokemons.$.health": 150}} # изменяемые параметры ) # Оператор '$' определяет, какой элемент документа запроса соответствует, а затем применяем к нему соответствующие значения
ПРИМЕЧАНИЕ: оператор $ обновляет только первый элемент, а не все элементы в массиве. Что это значит ? Это означает, что если у тренера более одного пикачу, будет обновлен только первый пикачу в его массиве покемонов.
Использование опции фильтр массива
Вышеупомянутая проблема с позиционным оператором, который обновляет только первое совпадение в массиве, может быть легко решена с помощью фильтров массива. Это поможет вам выбрать все документы, которые вы хотите обновить. Давайте создадим ситуацию для запроса и решим ее.
Предположим, мы хотим найти тренеров с experience 200 и выше и научить их покемонов типа grass движению «Solar beam». У тренера может быть несколько покемонов типа grass, поэтому мы должны обучать всех покемонов этого типа.
Запрос:
> db.trainers.updateMany( {experience: {$gte: 200}}, {$set: {"pokemons.$[poke].move": "Solar beam"}}, {applyFilters: [{"poke.type": "grass"}]} )
Объяснение:
- Во-первых, тут используется фильтр документов, чтобы отфильтровать документы из коллекции.
- Далее как видите, мы использовали синтаксис $[poke]. Здесь poke — это идентификатор, который поможет нам фильтровать документы в фильтрах массива. Вы можете использовать любое имя. Я использовал poke, вы можете использовать что-то вроде elem, el и т. д.
- Таким образом, у нас есть объект, в котором мы определяем фильтры массива.
- Далее мы использовали applyFilters, чтобы определить фильтры для идентификаторов, которые мы указали в $set
- У вас может быть только один фильтр для каждого идентификатора.
// это не сработает [{"poke.type": "grass"}, {"poke.level": {$gte: 14}}] // вместо этого используйте $and, $or или другие операторы [{ $and: [ {"poke.type": "grass"}, {"poke.level": {$gte: 14}} ] }]
Обратитесь к документации для получения более подробной информации.
Обратите внимание, что в документации используется метод update, а не updateOne или updateMany. Поэтому, когда они хотят использовать updateMany, они передают {multi: true} в параметрах.
На этом все! Если есть вопросы, задавайте их в комментариях (в блоге автора статьи). Я постараюсь ответить на них.
Хорошая и понятная статья, но в этом решении мне помог не applyFilters, а arrayFilters