Этот пост — небольшая шпаргалка по mongodb и немного длинных запросов с парой рецептов. Иногда бывает удобно когда какие-то мелочи собраны в одном месте, надеюсь, каждый, кто интересуется mongodb, найдет для себя что-то полезное. Не хотелось бы, чтобы пост воспринимался в ключе холиваров на тему SQL vs. NOSQL И так понятно что везде есть свои плюсы и минусы, в данном случае это просто где-то немного справки, где-то немного примеров из того, с чем приходилось сталкиваться. Примеры на mongo shell и на python. После выхода версии 2.6 в mongodb была добавлена новая система назначения прав пользователей на базы, отдельные коллекции. И, соответственно, при обновлении это нужно учитывать. 1) Необходимо переходить с версии 2.4 на версию 2.6. С 2.2 на 2.6 перейти не получится нет обратной совместимости, поэтому нужно поэтапно обновлять. Собственно, само обновление: 2) После того как обновились до 2.6 нужно зайти в базу admin и выполнить несколько команд, которые проверят совместимость документов. 3) Поскольку c версии 2.6 в mongodb, как уже было сказано, появились разграничения по ролям и выставление прав для любого пользователя вплоть до коллекции на чтение, запись и т.д., то соответственно надо задать эти роли, иначе не сможете выполнить команду auth.  Для этого вначале надо создать пользователя "Администратор" в базе admin 4) После этого зайти в свою нужную базу, с которой собрались работать и к которой хотим подключиться, и создаем там пользователя. Автоматически запись будет создана в базе admin в коллекции system.users
Просмотреть пользователей базы можно командой: Ну и не забываем перегрузить после всего этого. В ubuntu c этой версии сервис называется не mongodb а mongod а конфиг в /etc называется mongod.conf скорее всего, это связано с отсутствием обратной совместимости чтобы при обновлении не перепутать. О новой версии 3.0 и революционных изменениях в движке хранилища было уже написано много, не буду повторяться.  Перед обновлением до 3.0 рекомендуется последовательно обновиться до версии 2.6 не перескакивая. То есть 2.2->2.4->2.6.
Последняя версия рекомендуется не ниже 2.6.5.
Сама установка для ubuntu довольно стандартна
Добавляем репозиторий 3-й версии: Для Ubuntu 15.04 Устанавливаем: Для каждого компонента указываем версию при установке. После этого:  Смотрим версию mongodb: Если версия 3 то все прошло нормально и теперь можно сменить хранилище. По умолчанию стоит MMAPv1. Для смены в /etc/mongo.conf ставим опцию: Подробнее про возможные опции связанные с новым хранилищем тут
И смотрим чтоб директория /var/lib/mongodb была пустой иначе mongodb не запустится, естественно перед этим для всех баз нужно сделать mongodump Проверяем версию движка для хранилища: Ищем storageEngine если wiredTiger то все нормально. Теперь нужно импортировать базы, включая admin Вместе с новой версией базы, вышла новая версия драйвера для Python PyMongo, в ней удалили некоторые устаревшие методы и после: Даже без обновления самой базы, не все будет работать как прежде. Из того, что сразу было замечено: Пример использования: $eq сравнивающий оператор
Оператор $eq эквивалентен db.test.find({ field: <value> }) $gt больше чем
$gt выбирает те документы, где значение поля больше (>) указанного значения. $gte больше или равно чем
$gte выбирает те документы, где значение поля больше или равно (>=) указанного значения. $lt меньше чем
$lt выбирает те документы, где значение поля меньше (<) указанного  $lte меньше или равно чем
$lte выбирает те документы, где значение поля меньше или равно (<=) указанного  Если прибыль продавца меньше 100, то премия аннулируется. $ne не равно
$ne выбирает документы, где значение поля не равно (! =) указанному значению. $in проверка на вхождение Пример с регулярным выражением $nin проверка на невхождение
Тоже что и $in но наоборот, проверяет, что какое-то значение отсутствует в массиве. $or оператор или
Классический оператор или, берет несколько значений и проверяет что условию соответствует хотя бы одно из них. Для этого запроса предлагается составить два индекса: Если $or используется вместе с оператором $text, предназначенным для полнотекстового поиска то индекс должен быть обязательно. $and оператор "и"
Оператор и проверяет присутствие всех перечисленных значений в искомых документах. Пример вместе с $or: $not оператор отрицания
Проверяет чтобы в выборке не было документов, соответствующих условию. Пример с регулярным выражением: $nor оператор не или Этот запрос в коллекции test найдет те документы в которых: $exists проверка поля на существование
$exists извлекает те документы, в которых определенный ключ присутствует или отсутствует.
Если укажем у $exists в качестве параметра false, то запрос вернет те документы, в которых не определен ключ qty. $type проверка BSON типа $mod
Оператор $mod используется для выборки полей, значения которых делятся на первый аргумент и остаток от деления равняется второму.
Например, есть документы: Запрос: Вернет следующие документы: Аналог из SQL  C 2.6 версии запрещено передавать только один элемент, будет возращена ошибка. Также, если передать больше трех аргументов, тоже выдаст ошибку, в предыдущих версиях лишние аргументы просто игнорировались.
$all выбрать соответствующие всем
Делает выборку больше чем по одному элементу массива. $elemMatch
Используется когда нужно сравнить два и более атрибутов, принадлежащих одному поддокументу.
Проверяет, что в массиве есть элемент подпадающий под все условия.  Получаем результат: Еще пример: $size ищет по длине массива
Оператор $size находит документы, в которых количество элементов массива равно значению $size. Например, извлечем все документы, в которых в массиве laguages два элемента: Такой запрос будет соответствовать, например, следующему документу: $ позиционный оператор
$ может использоваться в разных случаях. Когда мы не знаем под каким индексом лежит значение в массиве но хотим его использовать, то применяем "позиционный оператор"
Например, есть документы: И мы хотим, чтобы после поиска по ним вывелось только одно значение, соответствующее запросу, а не весь документ, но заранее мы не знаем какое значение там стоит. Пример для update: $slice находит диапазон
$slice — находит диапазон значений хранящихся в массиве.
Найти первые 10 событий: Найти последние 10 событий: На Хабре была неплохая публикацияпро полнотекстовый поиск вmongodb` но с того времени добавились новые операторы и новые возможности.
Текстовый поиск не работает без индексов, поэтому поговорим о них.
Простой индекс создается по любому текстовому полю или массиву. 
 Можно использовать название поля или подстановочный спецификатор: При создании индекса для полнотекстового поиска надо учитывать язык, если это не английский. Начиная с версии 2.6 появилась возможность задания текстового индекса сразу для многих языков. MongoDB будет использовать указанный в документе язык при построении индекса. Язык указанный в документе перекрывает язык по умолчанию. Язык во встроенном документе переопределяет все остальные для индекса. Также, есть возможность с помощью параметра language_override указывать поле с языком.
Например, для документов: Индекс будет выглядеть таким образом: Индексу можно назначать специальное имя { name: "name" }, например: Имя удобно использовать для удаления индексов: Также, для текстового индекса можно задавать значимость, вес поля для поиска.
Например установим вес для следующих полей: content - 10, keywords - 5, и title - 1. Через индекс можно ограничить кол-во записей в выдаче: На выходе будет только один документ вместо двух, так как мы ограничили в индексе.
Пример индекса для Python: Сразу после появления текстового поиска в mongodb он осуществлялся с помощью runCommand например: но, начиная с версии 2.6, появился новый оператор $text
Поиск по одному слову: Поиск по нескольким словам: Поиск по фразе: Исключение поля из поиска через - Также с mongodb 2.6 появился еще один оператор $meta, показывающий точность совпадения результата с запросом.  Тут { score: { $meta: "textScore" } } мы создаем новое поле в его значении содержится результат и дальше оно уже участвует в сортировке.
Поиск через $regex
MongoDB использует Perl-совместимые регулярные выражения.  i — Нечувствительность к регистру.
Аналог из PostgreSQL Как правило все эти модификаторы используются для операций обновления в db.test.update() и db.test.findAndModify() $inc инкремент
Увеличивает или уменьшает поле на заданное значение $mul мультипликативный инкремент
Умножает значение поля на заданную величину. $rename переименование поля $set изменяет значение полей
Наверно это основной модифицирующий оператор, применяемый вместе с update. Часто о нем вспоминают как о простеньких транзакциях в контексте mongodb. $setOnInsert добавляет поля в новый документ
В update третьим аргументом идет опция { upsert: true } это значит, что если документ для изменения не найден, то мы создаем новый. А опция $setOnInsert говорит нам какие поля туда вставить. Поле, для которого мы выполняем $set тоже появится в новосозданном документе.
$unset удаляет ключ $min обновляет, если меньше
$min обновляет поле, если указанное значение меньше текущего значения поля, $min может сравнивать значения различных типов. $max обновляет если больше
$max обновляет поле, если указанное значение больше текущего значения поля. $currentDate устанавливает текущую дату
Устанавливает значением поля текущую дату. $addToSet добавляет значение, если его нет
Добавляет значение в массив, если его там еще нет, а если есть, то ничего не делает. $pop удаляет 1-й или последний
Удаляет первый или последний элемент массива. Если указано -1 то удалит первый элемент, если указано 1, то последний. $pullAll удаляет все указанные
Удаляет все указанные элементы из массива. $pull удаляет в соответствии с запросом $push добавляет значения
Добавляет значения в массив. $pushAll — считается устаревшим $each сразу много
Добавляет каждый из перечисленных элементов в массив
Например если мы сделаем так: { $push: { scores: [ 2, 10 ] } }
То на выходе получится такой массив: "scores" : [7, 8, 9, 90, 92, 85, [ 2, 10 ] ]
то есть добавился еще один элемент являющийся массивом.
А если через $each, то добавится каждый элемент списка как элемент массива: $slice ограничивает количество элементов при использовании $push
Ограничивает количество элементов массива при вставке с помощью $push. Обязательно использует $each если попытаться без него использовать, то вернет ошибку. $slice отрезал первый элемент 20. если бы мы указали не -5 а 5 то он бы откинул последний элемент 70. $sort сортировка элементов массива
Сортирует элементы массива в соответствии с указанным полем. Также обязательно использовать с оператором $each. Если нужно просто отсортировать без вставки, то $each можно оставить пустым. Еще пример: $position указывает позицию вставки
Указывает с какого по счету элемента массива вставить значения. $bit побитово обновляет
Выполняет побитовое обновление поля. Оператор поддерживает побитовые and, or и xor. $isolated — атомизация
Блокирует документ для чтения и записи, пока с ним происходит, например, операция обновления.
Использование $isolated при удалении: Использование $isolated при обновлении: $isolated не работает с шардированными кластерами
С версии 2.2: оператор $isolated заменил $atomic Естественно таких транзакций как в классических SQL решениях типа PostgreeSQL в MongoDB нет и наверно не может быть. А если появится, то это будет уже, скорее, реляционная база данных с полноценной нормализацией и контролем целостности.
Поэтому, говоря о транзакциях в mongoDB, как правило, имеют в виду атомарные операции типа $set, применяемые в update() и findAndModify() в сочетании с уникальным индексом. А также двухфазный коммит, который распространен среди реляционных баз данных, если нужно обеспечить транзакции в пределах нескольких баз.  Уникальный индекс в mongodb является причиной отклонить все документы, которые содержат повторяющиеся значения для индексированных полей. Есть коллекция, назовем её test, в этой коллекции нет документов у которых поле name имело бы значение Nik. Предположим, что сразу несколько клиентов одновременно пытается обновить этот документ с параметром { upsert: true } (означает, что если по условию нет такого документа для обновления, то его нужно создать).
Пример: Если все операции update() успешно выполнили запрос и нашли обновляемый документ прежде, чем любой из клиентов вставит свои данные, и нет уникального индекса на поле, то все операции обновления могут вставить данные. Для предотвращения вставки в один и тот же документ несколько раз, нужно создать уникальный индекс на поле. Тогда одна из операций обновлений точно вставит новый документ. Остальные операции либо обновят недавно вставленный документ, либо не смогут выполнить операцию если попытаются вставить дублирующие значения. По умолчанию unique является false в индексах MongoDB Рассмотрим пример из документации, операцию по переводу денежных средств со счета A на счет B.
У нас в примере есть две коллекции: Инициализация коллекций accounts и transactions
Вставляем в коллекцию accounts два документа соответственно для счетов А и В В коллекцию transactions для каждого перевода средств вставляем документ с информацией о транзакции. Где у нас есть следующие поля: 1) Получение документа с транзакцией 
Получаем документ с транзакцией, имеющей статус initial. И присваиваем его переменной t  2) Обновление статуса транзакции до состояния pending
Меняем состояние нужной транзакции с initial на pending и устанавливаем текущую дату. 3) Изменение обоих счетов
Изменяем баланс у обоих документов средства, одному увеличиваем на количество, равное полю value из документа транзакции, а в поле pendingTransactions заносим _id транзакции (документа где хранится информация о транзакции). 4) Обновление транзакции до состояния applied
Обновляем документ с транзакцией и не забываем установить дату последнего изменения. 5) Удаление _id транзакции из обоих документов
Находим оба документа в том числе по условию pendingTransactions: _id транзакции и очищаем у них поле pendingTransactions. 6) Обновление транзакции до состояния done
На этом двухфазный коммит завершён. Теперь рассмотрим случай если у нас остались несработавшие транзакции. В этом случае нам надо вернутся обратно и завершить.
1) Устанавливаем состояние транзакции canceling 
Находим все документы, которые были в ожидании, и устанавливаем состояние canceling. 2) Отменяем транзакцию для обоих счетов
Возвращаем средства обратно на счет с которого переводили. 3) Устанавливаем состояние транзакции cancelled
Обновляем состояние с отменяемой до отмененной. Когда выполняются несколько приложений, важно чтобы только одно приложение обращалось к транзакции в одно и тоже время. Поэтому дополнительно нужно чтобы документ кроме состояния хранил еще и идентификатор приложения.
Также рекомендуется использовать метод findAndModify(), чтобы изменить транзакцию и получить документ с ней обратно в один шаг: Когда говорят про JOIN-ы в mongo или спрашивают про них, почему-то часто речь заходит о связывании отдельных коллекций. Часто такие подобные вопросы мелькают на stackoverflow, но следует понимать, что это невозможно.  И, как правило, всегда рекомендуется документы, которые могут участвовать в одном запросе, помещать в одну коллекцию.  А если потом есть желание разбить их на какие-то группы по определённым признакам, то различать их по одному из полей, например { type: 'news' }. Но бывают случаи когда действительно надо взять несколько документов, у которых нет практически ничего общего, и объединить их в один документ. К сожалению, простого пути в данном случае нет и нужно делать либо несколько запросов, либо лепить довольно длинную цепочку в aggregation framework. Но на этом примере можно хорошо понять как работает pipeline. Вообще, это очень удобная штука, можно взять выборку документов и проделывать с ними любые операции, объединять, разбивать, группировать и т.д. Предположим, у нас есть две разновидности документов, в одной находятся пользователи и перечисляется группы к которым они относятся. И есть документы в которых находятся статьи написанные этими пользователями. Задача получить на выходе в одном документе пользователя, статьи которые он написал и группы в которых состоит. Конечно если бы мы это делали несколькими запросами, то все выглядело довольно просто. Выбрать из блогов только те статьи, где пользователь, опубликовавший материал, состоит в группе "автор". Примерный аналог c джойном из SQL если мы членство в группах храним в таблице: Или если мы названия групп храним прямо в таблице user, если всё хранится в поле d типа jsonb. Селектами делается два списка — и дальше на них накладываются ровно те же условия: Теперь попробуем повторить примерно то же самое с помощью pipe. Теперь разберем по порядку что делает запрос. Запрос состоит из 7 частей.
Как правило, в справке по mongodb и статьях про pipeline приводят эту табличку. Она не совсем отражает смысл каждого оператора, но, имея её перед глазами, немного помогает ориентироваться в длинных цепочках, ну и не забывать, что порядок может быть абсолютно любой и повторяться каждый оператор может много раз.  Сначала мы находим все документы с которыми будем работать. Сам по себе оператор $match похож на find(), единственное его преимущество в том, что он может встраивается в цепочку.
Дальше c помощью $project мы формируем новые документы, с основными полями blogs и users. Там мы используем появившийся с версии 2.6 оператор $cond который позволяет писать внутри себя логические выражения. Проверяем тип документа и уже по результату формируем поля blogs и users, чтобы потом было удобно группировать. Следующим этапом группируем эти документы: Еще раз создаем новые документы, где главным является условие $eq:[ "$user._id", "$blog.user" ] в котором мы сравнивая значения двух полей "user" : { "_id" : 2 } и "blog" : { "user" : "vasya" } и маркируем документы которые в последствии будут отфильтрованы и получен окончательный результат. И завершающая часть: { "_id": { "a": null }, "user": { "_id": "gomer", "group": [ "user", "author" ] }, "article": { "_id": 1, "user": "gomer", "article": "aaa" }, "matches": true }
{ "_id": { "a": null }, "user": { "_id": "vasya", "group": [ "user" ] }, "article": { "_id": 2, "user": "vasya", "article": "bbb" }, "matches": true }
{ "_id": { "a": null }, "user": { "_id": "gomer", "group": [ "user", "author" ] }, "article": { "_id": 3, "user": "gomer", "article": "ccc" }, "matches": true } Мы просто отфильтруем те документы, которые соответствовали предыдущему условию.
И теперь у нас есть документы, в которых указано какую статью написал каждый пользователь и в какой пользовательской группе он состоит. Конечно, запрос можно было бы написать немного компактней но задачей было показать как можно играться с данными посредством pipeline. В основном здесь речь пойдет, скорее, о структуре документов чем о запросах. Как правило есть два основных подхода обычно:  Как правило, одна из самых распространенных задач — это различные древовидные структуры. Это и комментарии, и каталог товаров в интернет магазинах, и схемы хранения на складах, и много чего еще.  Рассмотрим пару простых примеров.
В виде субдокументов: Просто документы: Сам по себе простой поиск по обоим вариантам будет работать достаточно эффективно.
Тем более, что по второму варианту можно создать индексы.
В некоторых случаях вариант с поддокументами нужен для хранения части информации, но в целом сами комментарии хранятся в поддокументах, такие варианты называют модным ныне словом "гибридная" схема. Теперь рассмотрим несколько примеров работы с документами.
Удалить одного или несколько детей у родителя: Добавить одного или несколько детей родителю: Сформируем дерево из полученных документов: Посчитаем количество товаров, которые относятся к каждой категории дерева с учетом всех вложенных категорий, при условии, что в каждом товаре хранится _id категории к которой он принадлежит: Найдем путь к началу, зная _id категории, после чего останется в шаблоне просто фором пройтись по списку кортежей: Чаще всего для хранения тегов используется гибридный вариант, то есть они хранятся например в поле { tags : { ru: "один, два" } }, чтобы их сразу удобно было выводить на странице под материалом. И хранятся в массиве для удобного поиска по ним { tags : [ "один", "два" ] }.
Облака тегов иногда тоже отдельно хранят, иногда формируют на лету.
Поиск по массиву тегов, если, к примеру, нужно найти список документов: Ну и теперь отфильтруем: Теперь все, что нам нужно — разделить область видимости сайта на ячейки и для каждой из них можно предоставлять свой контент. Наверное, одна из основных сложностей и задач веб-магазинов и различных учётов — это все-таки фильтры. Они нужны как для создания хитрых отчетов, так и для просто показа где что лежит на складе, ну и, банально, отфильтровать ноутбуки по диагонали, цене, производителю и прочему.  Последним случаем, из-за слишком большого объема материала, мы и ограничимся. Остальные рассмотрим в последующих публикациях. Существует два вида фильтров, или, если хотите, способа организации контента в веб-магазине.  В первом случае, поскольку наличествующие атрибуты у нас просто текстовые поля, ни с чем не связанные, то нам нужно просто по ним сгруппировать документы — и всё. Но в таком случае при неправильном заполнении, а также при развитии учета, в магазине возникнут проблемы и придется все перезаносить. Во втором случае нам надо для каждой категории товаров настроить свои фильтры и каждый из фильтров тем или иным способом привязать к характеристикам товаров (атрибутам). Работа с фильтрами состоит из двух этапов: Начнем с показа значений фильтра, если у нас настроена привязка фильтров к характеристикам товаров, тогда все выглядит довольно просто. И делается простым запросом: Представить данные можно по разному, например, можно сделать древовидной таблицей, где первым уровнем будут названия фильтров, а вторым уровнем его характеристики. Название => Диагональ
Характеристики=> 15.6 дюйма, 17 дюймов и тд. Можно по другому представить, пойти как на мой взгляд немного более распространенным путем. Сделать подтаблицами по типу приходных и расходных накладных. Когда есть шапка документа с перечислением реквизитов и тд., и есть список товаров снизу.
Только в данном случае шапка — это название категории, для которой мы настраиваем фильтры, а список товаров — это список названий фильтров с атрибутами в одной из подчиненных таблиц. В этом случае запрос будет немного посложнее и придется группировать данные. Это уже будет похоже на аналогичный запрос к получению фильтров из самих товаров, этот запрос и опишем. У нас есть список товаров, у каждого есть список атрибутов хранящихся или в отдельных документах, или в нем самом в субдокументах. Визуально это может выглядеть примерно так. В нашем случае возьмем чуть более сложный вариант и предположим, что это будут отдельные документы. Желательно чтобы _id категории хранилось тогда в каждом документе с характеристикой.
Оператор $addToSet при группировке выбирает уникальные элементы из массива. После первого group мы получаем документы такого типа: После второго group мы уже получаем окончательный вариант. Теперь, когда мы уже имеем список фильтров, можно поискать по этим фильтрам. Поскольку мы ищем по субдокументам, то есть по характеристикам, то группируем по owner_id это _id документа к которому принадлежат характеристики. На выходе получаем список _id документов подходящих под фильтры. Как правило при работе с различными типами данных удобно визуализировать в виде различных иерархических и других табличек.  Но часто бывает, что нужно повесить на изменение, к примеру, поля или удаление документа (читай — строки в таблице) какое либо событие. Например, мы поменяли в справочнике валют курс по которому закупаем, и хотим чтоб цены в отечественной валюте пересчитались для всех товаров.  Это достаточно стандартный функционал для различных ERP решений. И, поскольку часто неизвестно кто будет писать для песочницы код, нужна возможность запускать чужой код более-менее безопасно. На данный момент есть библиотеки, которые дают эту возможность, но точно неизвестно как хорошо они ограничивают запускаемый код. Есть простой способ безопасно выполнять чужой код в python, он привносит некоторые ограничения, но для большинства задач его достаточно. Пример с exec Пример с eval (для вычисления выражения) Главное — это:
assert '__' not in src, 'Prohibited to use symbols "__"' тут мы заменяем весь базовый функционал (import, type...) на нужный/разрешенный функционал.  Full-stack Web Developer