Статьи

Начало работы с MongoDB — часть 2

Готовы продолжить изучение MongoDB, одной из самых крутых технологий для веб-разработчиков? Во второй части серии мы переходим от основ к более сложным запросам — с условными операторами — и MapReduce.


Ранее мы рассмотрели основы и как начать работу с mongoDB , одной из абсолютных лучших реализаций NoSQL . Мы рассмотрели, как установить его, создать базовую базу данных и затем выполнить основные операции с ней:

  • поиск
  • Обновить
  • удалять

В дополнение к этому мы начали смотреть на то, как начать взаимодействовать с mongoDB более мощным способом, используя селекторы . Селекторы дают нам возможность иметь гораздо более детальный контроль и довольно глубоко копаться в поисках данных, которые нам действительно нужны.

Теперь все хорошо, но когда вы хотите написать реальное приложение, вам нужно пойти намного дальше. Ну, в конце концов, эта серия еще только начинается , но я хочу, чтобы вы взволнованы возможностями работы с документами. Я хочу побудить вас взять эту замечательную технологию, сделать ее своей и использовать ее настолько мощно, насколько это возможно, для создания фантастических приложений.

Сегодня мы рассмотрим запросы последнего времени и изучим два ключевых аспекта mongoDB:

  • Расширенные запросы
  • Уменьшение карты

Ранее мы рассмотрели основные запросы и были представлены селекторам. Теперь мы собираемся перейти к более сложным запросам, опираясь на предыдущую работу двумя ключевыми способами:

  • Условные операторы
  • Регулярные выражения

Каждый из них последовательно предоставляет нам более детальный контроль над запросами, которые мы можем написать, и, следовательно, информацией, которую мы можем извлечь из наших баз данных mongoDB.

Условные операторы, как следует из названия, являются операторами для сбора запросов, которые уточняют условия, которым должен соответствовать запрос при извлечении данных из базы данных. Их много, но сегодня я собираюсь сосредоточиться на 9 ключевых. Эти:

  • $ lt — значение должно быть меньше условного
  • $ gt — значение должно быть больше условного
  • $ lte — значение должно быть меньше или равно условному
  • $ gte — значение должно быть больше или равно условному
  • $ in — значение должно быть в наборе условий
  • $ nin — значение НЕ должно быть в наборе условий
  • $ not — значение должно быть равно условному

Давайте посмотрим на каждого по очереди. Откройте свой терминал и приготовьтесь использовать исходную базу данных из первой части этой серии (предварительные модификации). Чтобы упростить этот урок, мы немного изменим базу данных. Мы собираемся дать каждому документу в нашей коллекции атрибут возраста. Для этого выполните следующий запрос на изменение:

1
2
3
4
5
6
7
db.nettuts.update({«_id» : ObjectId(«4ef224be0fec2806da6e9b27»)}, {«$set» : {«age» : 18 }});
db.nettuts.update({«_id» : ObjectId(«4ef224bf0fec2806da6e9b28»)}, {«$set» : {«age» : 45 }});
db.nettuts.update({«_id» : ObjectId(«4ef224bf0fec2806da6e9b29»)}, {«$set» : {«age» : 65 }});
db.nettuts.update({«_id» : ObjectId(«4ef224bf0fec2806da6e9b2a»)}, {«$set» : {«age» : 43 }});
db.nettuts.update({«_id» : ObjectId(«4ef224bf0fec2806da6e9b2b»)}, {«$set» : {«age» : 22 }});
db.nettuts.update({«_id» : ObjectId(«4ef224bf0fec2806da6e9b2c»)}, {«$set» : {«age» : 45 }});
db.nettuts.update({«_id» : ObjectId(«4ef224bf0fec2806da6e9b2d»)}, {«$set» : {«age» : 33 }});

Если все хорошо, вы можете запустить ‘find all’ и получить следующий вывод:

1
2
3
4
5
6
7
8
db.nettuts.find();
{ «_id» : ObjectId(«4ef224be0fec2806da6e9b27»), «age» : 18, «dob» : «21/04/1978», «first» : «matthew», «gender» : «m», «hair_colour» : «brown», «last» : «setter», «nationality» : «australian», «occupation» : «developer» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b28»), «age» : 45, «dob» : «26/03/1940», «first» : «james», «gender» : «m», «hair_colour» : «brown», «last» : «caan», «nationality» : «american», «occupation» : «actor» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b29»), «age» : 65, «dob» : «03/06/1925», «first» : «arnold», «gender» : «m», «hair_colour» : «brown», «last» : «schwarzenegger», «nationality» : «american», «occupation» : «actor» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b2a»), «age» : 43, «dob» : «21/04/1978», «first» : «tony», «gender» : «m», «hair_colour» : «brown», «last» : «curtis», «nationality» : «american», «occupation» : «developer» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b2b»), «age» : 22, «dob» : «22/11/1958», «first» : «jamie lee», «gender» : «f», «hair_colour» : «brown», «last» : «curtis», «nationality» : «american», «occupation» : «actor» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b2c»), «age» : 45, «dob» : «14/03/1933», «first» : «michael», «gender» : «m», «hair_colour» : «brown», «last» : «caine», «nationality» : «english», «occupation» : «actor» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b2d»), «age» : 33, «dob» : «09/12/1934», «first» : «judi», «gender» : «f», «hair_colour» : «white», «last» : «dench», «nationality» : «english», «occupation» : «actress» }

Теперь давайте найдем всех актеров, которым меньше 40 лет. Для этого выполните следующий запрос:

1
db.nettuts.find( { «age» : { «$lt» : 40 } } );

После выполнения этого запроса вы увидите следующий вывод:

1
2
3
{ «_id» : ObjectId(«4ef224be0fec2806da6e9b27»), «age» : 18, «dob» : «21/04/1978», «first» : «matthew», «gender» : «m», «hair_colour» : «brown», «last» : «setter», «nationality» : «australian», «occupation» : «developer» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b2b»), «age» : 22, «dob» : «22/11/1958», «first» : «jamie lee», «gender» : «f», «hair_colour» : «brown», «last» : «curtis», «nationality» : «american», «occupation» : «actor» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b2d»), «age» : 33, «dob» : «09/12/1934», «first» : «judi», «gender» : «f», «hair_colour» : «white», «last» : «dench», «nationality» : «english», «occupation» : «actress» }

А как насчет тех, кто меньше 40 включительно? Запустите следующий запрос, чтобы вернуть этот результат:

1
db.nettuts.find( { «age» : { «$lte» : 40 } } );

Это возвращает следующий список:

1
2
3
{ «_id» : ObjectId(«4ef224be0fec2806da6e9b27»), «age» : 18, «dob» : «21/04/1978», «first» : «matthew», «gender» : «m», «hair_colour» : «brown», «last» : «setter», «nationality» : «australian», «occupation» : «developer» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b2b»), «age» : 22, «dob» : «22/11/1958», «first» : «jamie lee», «gender» : «f», «hair_colour» : «brown», «last» : «curtis», «nationality» : «american», «occupation» : «actor» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b2d»), «age» : 33, «dob» : «09/12/1934», «first» : «judi», «gender» : «f», «hair_colour» : «white», «last» : «dench», «nationality» : «english», «occupation» : «actress» }

Теперь давайте найдем всех актеров старше 47 лет. Чтобы найти этот список, выполните следующий запрос:

1
db.nettuts.find( { ‘age’ : { ‘$gt’ : 47 } } );

Затем вы получите следующий вывод:

1
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b29»), «age» : 65, «dob» : «03/06/1925», «first» : «arnold», «gender» : «m», «hair_colour» : «brown», «last» : «schwarzenegger», «nationality» : «american», «occupation» : «actor» }

А как насчет 40?

1
db.nettuts.find( { ‘age’ : { ‘$gte’ : 47 } } );

Поскольку есть только один человек старше 47 лет, возвращенные данные не меняются.

Как насчет поиска информации на основе списка критериев? Эти первые были в порядке, но, возможно, довольно тривиально. Давайте теперь посмотрим, кто из нас — актеры или разработчики. С помощью следующего запроса мы выясним это (чтобы его было легче читать, мы ограничили ключи, которые возвращаются только именами и фамилиями):

1
db.nettuts.find( { ‘occupation’ : { ‘$in’ : [ «actor», «developer» ] } }, { «first» : 1, «last» : 1 } );

Этот запрос дает следующий вывод:

1
2
3
4
5
6
{ «_id» : ObjectId(«4ef224be0fec2806da6e9b27»), «first» : «matthew», «last» : «setter» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b28»), «first» : «james», «last» : «caan» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b29»), «first» : «arnold», «last» : «schwarzenegger» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b2a»), «first» : «tony», «last» : «curtis» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b2b»), «first» : «jamie lee», «last» : «curtis» }
{ «_id» : ObjectId(«4ef224bf0fec2806da6e9b2c»), «first» : «michael», «last» : «caine» }

Вы можете видеть, что мы можем получить обратное к этому, просто используя $nin .

Давайте сделаем это немного веселее и объединим некоторых операторов. Допустим, мы хотим найти всех людей, будь то мужчины или разработчики, им меньше 40 лет.

Это немного глоток, но с операторами, которые мы использовали до сих пор — вполне достижимо. Давайте поработаем над этим, и вы увидите. Посмотрите на запрос ниже:

1
db.nettuts.find( { $or : [ { «gender» : «m», «occupation» : «developer» } ], «age» : { «$gt» : 40 } }, { «first» : 1, «last» : 1, «occupation» : 1, «dob» : 1 } );

Вы можете видеть, что мы предусмотрели, что либо пол может быть мужчиной, либо профессия может быть разработчиком в $or условии, а затем добавлено and условие возраста, превышающего 4.

Для этого мы получаем следующие результаты:

1
2
{ «_id» : ObjectId(«4ef22e522893ba6797bf8cb6»), «first» : «matthew», «last» : «setter», «dob» : «21/04/1978», «occupation» : «developer» }
{ «_id» : ObjectId(«4ef22e522893ba6797bf8cb9»), «first» : «tony», «last» : «curtis», «dob» : «21/04/1978», «occupation» : «developer» }

Теперь я уверен, что вы не будете удовлетворены только этим. Я обещал вам еще больше сложности и расширенной функциональности. Итак, давайте перейдем к использованию некоторых регулярных выражений. Допустим, мы хотим найти пользователей, чье имя начинается с «ma» или «to», а фамилии начинаются с «se» или «de». Как бы мы это сделали?

Посмотрите на следующий запрос, используя регулярное выражение:

1
db.nettuts.find( { «first» : /(ma|to)*/i, «last» : /(se|de)/i } );

Учитывая это, результаты будут:

1
2
{ «_id» : ObjectId(«4ef22e522893ba6797bf8cb6»), «first» : «matthew», «last» : «setter», «dob» : «21/04/1978», «gender» : «m», «hair_colour» : «brown», «occupation» : «developer», «nationality» : «australian» }
{ «_id» : ObjectId(«4ef22e532893ba6797bf8cbc»), «first» : «judi», «last» : «dench», «dob» : «09/12/1934», «gender» : «f», «hair_colour» : «white», «occupation» : «actress», «nationality» : «english» }

Давайте посмотрим на этот запрос более внимательно. Во-первых, мы выполняем регулярное выражение для имени.

1
«first» : /(ma|to)*/i

//i указывает, что мы выполняем регулярное выражение без учета регистра.

(ma|to)* указывает, что начало строки имени должно быть либо «ma», либо «to».

Если вы не знакомы, * в конце будет соответствовать чему-либо после этого. Поэтому, когда вы соединяете это, мы сопоставляем имена, в начале которых есть либо «ma», либо «to». В регулярном выражении для фамилии вы можете видеть, что мы сделали то же самое, но для фамилии.

Не совсем уверен? Давайте попробуем еще один. Как насчет объединения его с одним из условных операторов. Допустим, мы хотим найти всех людей с именем Джеймс или Джейми, которые являются американскими актерами женского пола. Как бы мы это сделали? Что ж, посмотрим, как мы это сделаем, ниже:

1
db.nettuts.find( { «first» : /(jam?e*)*/i, «gender» : «f», «occupation» : «actor», «nationality» : «american» } );

Приведенное выше регулярное выражение будет соответствовать комбинациям, таким как: james, jamie, jamee и т. Д. Знак вопроса будет соответствовать одному символу, будь то az, AZ или 0-9. Затем, как и раньше, * соответствует всему, что идет после «е». С этого момента мы используем условные операторы до этого, чтобы еще больше ограничить возвращаемые результаты. Следует отметить, что, поскольку мы используем оператор без учета регистра, запросы не будут использовать индекс. Но для целей этого примера это хорошо.

Результат запроса выше:

1
{ «_id» : ObjectId(«4ef22e522893ba6797bf8cba»), «first» : «jamie lee», «last» : «curtis», «dob» : «22/11/1958», «gender» : «f», «hair_colour» : «brown», «occupation» : «actor», «nationality» : «american» }

MapReduce — большой папа анализа данных. Если вы не слышали об этом, MapReduce — это процесс, в котором объединение данных может быть разделено и распределено по кластеру компьютеров, чтобы сократить время, необходимое для определения совокупного результата для набора данных.

Он состоит из двух частей: Map и Reduce . Map создает задания, которые затем могут быть переданы на рабочие узлы для запуска компонента Reduce. Затем Reduce вычисляет ответ для той части работы, которая была ему передана, и возвращает результат, который можно объединить с другими частями для формирования окончательного ответа.

Если вы хотите более конкретное описание, вот что Википедия может сказать об этом:

MapReduce — это платформа для обработки проблем с высокой степенью распределения по огромным наборам данных с использованием большого количества компьютеров (узлов), которые вместе называются кластером (если все узлы используют одинаковое оборудование) или сеткой (если узлы используют разное оборудование). Вычислительная обработка может происходить с данными, хранящимися либо в файловой системе (неструктурированной), либо в базе данных (структурированной).

Шаг «Карта» : главный узел принимает входные данные, разделяет их на более мелкие подзадачи и распределяет их по рабочим узлам. Рабочий узел может сделать это снова по очереди, что приведет к многоуровневой древовидной структуре. Рабочий узел обрабатывает меньшую проблему и передает ответ своему главному узлу.

Шаг «Уменьшить» : Затем главный узел собирает ответы на все подзадачи и каким-то образом объединяет их для формирования выходных данных — ответа на проблему, которую он первоначально пытался решить.

Давайте посмотрим на простой пример. Мы собираемся проанализировать наш простой набор данных и найти общее количество всех женщин в группе. Следует признать, что это очень упрощенный пример, но он фактически заложит основу для понимания того, как работает MapReduce.

Здесь мы собираемся создать функцию карты, которая собирает информацию в нашем наборе данных по полу человека и выдает счет 1 для каждого из них.

1
2
3
var map = function() {
    emit( { gender: this.gender }, { count: 1 } );
}

Это вернет вывод, подобный следующему:

1
{ ‘f’ : 1 }

Наша функция сокращения собирается взять выходные данные из функции карты и использовать их, чтобы сохранить промежуточную сумму для каждого пола. Посмотрите на функцию уменьшения ниже.

1
2
3
4
5
6
7
8
9
var reduce = function(key, values) {
    var result = { count : 0 };
 
    values.forEach(function(value){
        result.count += value.count;
    })
 
    return result;
}

Теперь мы соединили их, вызвав функцию mapReduce в нашей текущей базе данных. Мы передаем карту и сокращаем переменные, которые мы создали ранее, вызывая нашу map и reduce функции и определяя имя коллекции, в которой будет сохранен результат; в данном случае «пол» . Просто повторюсь, результат вызова функции mapReduce — это коллекция в нашей текущей базе данных; что мы можем перебирать, как и любую другую коллекцию. Посмотрите на код ниже:

1
var res = db.nettuts.mapReduce( map, reduce, { out : ‘gender’ } );

Когда Map-Reduce завершен, мы можем получить к нему доступ, как к обычной коллекции, запустив find функцию find как мы делаем ниже.

1
2
3
db.gender.find();
{ «_id» : { «gender» : «f» }, «value» : { «count» : 2 } }
{ «_id» : { «gender» : «m» }, «value» : { «count» : 5 } }

Здесь у нас теперь есть промежуточный итог по полу; 2 для женщин и 5 для мужчин — что соответствует нашему набору данных. Но что, если мы хотим фильтровать по женщинам в группе. Ну, не так много. Нам нужно только использовать предложение запроса, чтобы позволить нам сделать это. К счастью для нас, это будет выглядеть знакомо. Посмотрите на запрос ниже.

1
var res = db.nettuts.mapReduce( map, reduce, { out : ‘gender’, query : { «gender» : «f» } } );

Теперь, когда мы отобразим вывод, он будет выглядеть так:

1
2
db.gender.find();
{ «_id» : { «gender» : «f» }, «value» : { «count» : 2 } }

Существует ряд других параметров, которые вы можете передать в функцию mapReduce для дальнейшей настройки вывода.

  • sort — отсортировать возвращенный результат
  • limit — ограничить количество возвращаемых результатов
  • out — имя коллекции для сохранения результатов в
  • finalize — указать функцию для запуска после завершения процесса сокращения
  • область действия — укажите переменные, которые можно использовать на карте, уменьшите и завершите область действия функции.
  • jsMode — позволяет избежать промежуточного шага (между отображением и уменьшением) преобразования обратно в формат JSON
  • verbose — отслеживать статистику о процессе исполнения

Это было вихревое освещение некоторых из более сложных тем mongoDB. Но я надеюсь, что это даст вам еще больше вкуса того, что возможно с помощью этого замечательного инструмента.

Мы рассмотрели условные операторы: $lt, $gt, $lte, $gte, $in, $nin, $not и познакомились с введением в MapReduce. Я надеюсь, что вы многое из этого получили и узнаете больше о великолепном инструменте mongoDB.