MongoDb for developers. Неделя 2
Доброе время суток, хабр. Этот топик — продолжение цикла статей, основанных на материалах онлайн курса M101 от 10gen. Поскольку вторая неделя является одной из самых насыщенных, она будет разбита на две статьи. Сразу уточню — описанное в статье справедливо для mongo версии 2.6. В любом случае рекомендую почитать офф. документациюа при наличии свободного времени посмотреть курсы
Первая статья цикла
В этом посте мы рассмотрим CRUD операции. Примеры даны на js для встроенного шелла и ввиду тривиальности на другие языки как в первой статье примеры не портировались.
Монго предоставляет полный набор методов, для совершения CRUD операций.
Все методы CRUD в монго- драйверах, являются функциями (методами) своих языков программирования, а не средством для создания выражения отдельного языка (привет sql). MongoDb имеет специальный, работающий поверх TCP протокол, обеспечивающий передачу вызовов методов, и возвращение результатов их работы.Например запросив в шелле
db.users.findOne ({name: «Alice»}) Мы обращаемся к ссылке на объект базы данных, запрашиваем ее атрибут users и вызываем его метод findOne (из интерфейса коллекции), в который передаем объект, составляющий поисковой критерийМы постоянно работаем с объектами и их методами, а не со строками sql.
Шелл MongoDb представляет собой интерактивный JS интерпретатор, со встроенным драйвером. При входе, нам выводится информация о версии и название текущей базы данных. На эту информацию стоит обращать внимание, чтобы предотвратить действия с не целевой базой, да и за свежестью версии следить — нелишнее. Любую версию шелла, можно использовать с любой версией монго сервера, но некоторый свежие фичи, старой версией шелла могут не поддерживаться.Так как шелл является интерпретатором, мы можем использовать, для облегчения своей работы, различные валидные js конструкции.
Вот небольшой пример часто используемый на этапе разработки — очищает все коллекции без удаления индексов
var list=db.getCollectionNames (); for (var i = 0; i < list.length; i++) { if (list[i]!="system.indexes") db.getCollection(list[i]).remove(); } Шелл имеет несколько удобных функций, знакомых большинству пользователей linux.
Arrow Up / Arrow down — листание истории командctrl+A / Home — переход в начало строкиctrl+E / End — переход в конец строкиCtrl+B / Arrow left — сместить курсор влевоctrl+F /Arrow right -сместить курсор вправоTab Автокомплит названий функций и имен коллекций.
Чтобы лучше понимать, что происходит при наших запросах — стоит подробнее рассказать овнутреннем представлении данных в Монго.
Монго использует формат BSON (акроним от Binary JSON). С подробной спецификацией, вы можете ознакомится на сайте bsonspec.org. При создании запроса, драйвер кодирует объекты запроса (критерии выборки например), в BSON, а при обработке ответа производит обратную операцию.В зависимости от используемого языка программирования данные мапятся на сущности языка способные их отобразить.
Js- Object Python — списки, словари Php — массивы Главное что мы можем взять такую сущность, и сохранить в базе. Монго использует бинарное представление этих данных.Формат BSON поддерживает те же типы данных что JSON + плюс несколько типов для внутреннего использования.Драйверы так или иначе, поддерживают все типы используемые в BSON и данные используемого языка приводятся к одному из поддерживаемых типов. Это стоит учитывать, при работе с языками имеющими ограниченный набор типов.
Все нормальные коллекции имею атрибут _id — уникальный первичный ключ, про abnormal коллекции мы поговорим несколько позже. Его нельзя изменить — разве что удалив документ и вставив по новой.Если _id не был явно задан, при создании объекта, он формируется по довольно хитрому алгоритму, с использованием дополнительно повышающих энтропию величин
время + идентификатор клиентской машины+ идентификатор процесса создающего объект+ счетчик, глобальный для процессов монго.
В некоторых случаях удобнее явно определять _id явно, так например для коллекции хранящей пользовательские данные — в качестве идентификатора можно использовать логин пользователя. Для идентификатора можно использовать любой из поддерживаемых типов данных.
В предыдущей статье мы уже рассматривали создание новых документов в коллекции. Теперь рассмотрим, как забирать их обратно.Для выборки существует метод find (). В случаях, когда нам нужен лиш один элемент коллекции — удобно использовать findOne ().
findOne (criteria, fields) criteria — документ обозначающий поисковый критерий. По умолчанию пустой документ (которому соответствуют все элементы коллекции). fields — документ задающий, какие поля мы хотим получить, где в качестве значения для ключа — указывается логической значение. По умолчанию — все ключи trueУ второго документа есть одна особенность- поле _id возвращается всегда, если только явно не было указано что его не нужно возвращать.
Как говорится — лучше один раз увидеть, чем сто раз услышать
//Вставим пару документов в новую коллекцию > db.users.insert ({name: «Woody», «role»: «sheriff», age: «unknown»}) > db.users.insert ({name: «Buzz», «role»: «space ranger», age: «unknown»})
// Проведем запрос, использую критерий по полю name. Т.к второй аргумент опущен — нам вернется все содержимое документа > db.users.findOne ({name: «Woody»}) { »_id» : ObjectId (»509ffa90cc922c3538cd1ce1»), «name» : «Woody», «role» : «sheriff», «age» : «unknown» }
//Теперь попробуем получить только имя > db.users.findOne ({name: «Woody»}, {name: true}) { »_id» : ObjectId (»509ffa90cc922c3538cd1ce1»), «name» : «Woody» } // Как видите, мы также получили в результатах, первичный ключ- т.к мы не указали явно, что он нам ненужен
// Теперь запросим только имя и роль, без первичного ключа. Как видите в качестве значения можно использовать не только //булево значение, но и приводимые к нему типы > db.users.findOne ({name: «Woody»},{name:1, role:1,_id:0}) { «name» : «Woody», «role» : «sheriff» } > Поработаем с find () и рассмотрим операторы используемые для составления сложный поисковых критериев.
Кстати пока не забыл — если вы получаете большой набор данных, в шелле выводится ограниченная их часть (по дефолту 20) и вы можете ввести it для получения следующей части. Дело в том, что драйвер (и не только в шелле). В ответ на запрос не получает никаких данных. Он получает курсор на них. Курсор хранится на сервере некоторое время (по умолчанию 10 минут), затем удаляется.
Запрос фактически будет выполнен только тогда, когда мы начнем читать из курсора. Тогда же мы получим данные. До этого никаких действий не происходит.
Параметры у find () точно такие же, как и у findOnefind (criteria, fields)
Мы уже рассмотрели случай поиска по эквивалентности. Для составления более сложных условий ключу указывается документ с методами сравнения и параметрами для них.
$gt — , больше$gte — больше либо равно$lt — меньше$lte — меньше либо равно
Так будет выглядеть запрос в шелле, на выборку всех пользователей старше 20, но моложе 30 лет, с балансом от 100
db.users.find ({age:{$gt:20,$lt:30}, balance:{$gte:100}}); Как видите, количество ключей в документе-критерии произвольно.Операторы неэквивалентного сравнения могут применятся и к строкам
db.people.find ({name:{$lt: «D»}}) Монго ничего не знает о локалях — сравниваются utf коды символов. Важно незабывать, что так как Монго безсхемна — один и тот же ключ в разных документах одной коллекции может содержать данные разных типов. Операции сравнения в Монго типизированы Так если к нашей коллекции добавить документ с name int, поисковый запрос выше не вернет его в результатах поиска. Вообще хранить в одном ключе данные разных типов, хоть и возможно —, но не рекомендуется, в виду того что это усложняет выборку.
Операторы сортировки и сравнения чувстивтельны к региструПорядок использования операторов сравнения не важен.
Монго позволяет создавать запросы основанные не только на значениях хранимых в документе, но и на его структуре и типах данных.
db.people.find ({hobby:{$exists: true}}) $exists — существование элемента документаМожно создать запрос, основанный на типе определенного элемента документа
$type — проверка эквивалнтности BSON типа элемента заданному
db.people.find ({name:{$type:2}}) $regex — проверка элемента на соответствие заданному PCRE
// Вернет все документы поле email в которых оканчивается на nail.ru db.users.find («email»:{$regex: «mail.ru$»}}) Подробнее о регэкспах мы поговорим на след неделе, когда будем рассматривать вопросы производительности. Стоит отметить, что ненадо боятся снижения скорости, так например поиск по регекспу ^A транслитерируется движком в запрос по диапазону [A, B)
Посмотрим как можно составить условие ИЛИДля объединения нескольких условий с помощью логического ИЛИ, служит префиксный оператор $or$or:[массив условий]
db.users.find ({$or:[{age:{$gt:10}},{name:{$regex:»^A»}}]}); Стоит отметить что при составлении более сложных запросов в шелле весьма возрастает полезность встроенной подсветки границ элементов.
Для логической конъюнкции используется оператор $and. Он абсолютно идентичен $or по синтаксису и я не вижу смысла останавливатся на нем подробнее. Стоит отметить — что используется этот оператор редко, так как обычно запрос можно выразить проще
Работа с массивами В запросах по массивам монго проявляет полиморфность db.users.find ({favorites: «beer»}) Если в документе favorites — строка, то она попадет в выборку при эквивалентности, если же favorites массив, то он будет включен в выборку при эквивалентности хотя бы одного из элементов.Такое поведение является весьма распространенной в монго идиомой.Монго не проводит рекурсивный перебор массива, просматривается лишь верхний уровень.
Так же существует несколько специфичных операторов для поиска по массивам.$all — выбирает элеенты содержащие в массиве все перечисленные знаения> $db.users.find (favorites:{$all:{«beer», «milk»}})
$in — аналог сикуэловского оператора IN. Вернет все документы значение выбранного лемента которых перечислен в массиве. Запрос с использованием данного оператора всегда можно заменить запросом с $or
Запись с точкой (dot notation) В предыдущих примерах мы работали с документами содержащими строки, числа и массивы. Пора приступить в кложенным объектам.Монго поддерживает специальный синтаксис, позволяющий строить запросы по внутренним элементам вложенных документов.
db.users.find («email.work»:{$regex: «mail.ru$»}}) Предназначение dot notation просмотр элементов вложенных документов. Такой синтаксис может использоватся и для доступа к определенному элементу массиваTo be continued …