Получите представление о своих данных в MongoDB путем агрегирования
MongoDB — это не просто база данных документов NoSQL. Вы можете выполнять сложный анализ документов в коллекциях, используя конвейеры агрегации. Распространенной задачей является группировка результатов и получение общих или средних данных для каждой группы. Вы даже можете фильтровать и конвертировать или очищать данные перед группировкой.
Конвейер агрегации состоит из одного или нескольких этапов, последовательно обрабатывающих документы. Каждый этап выполняет некоторую операцию над входными документами и передает обработанные на следующий этап. Например, на первом этапе можно фильтровать документы по некоторым условиям, на втором — группировать отфильтрованные документы и выполнять некоторые агрегации, а на третьем — выводить результаты.
Мы будем использовать данные, представленные в статье для расширенных запросов MongoDB, для демонстрации в этом посте. Если вы хотите продолжить, полезно проверить шаги по настройке системы в этом посте, чтобы лучше узнать данные. Однако, если вы спешите, вы можете просто скачать этот файл JSON и использовать следующие команды для импорта данных:
В этом посте мы используем Docker для локального запуска сервера MongoDB, но вы можете использовать размещенный сервер, такой как MongoDB Atlas, который имеет бесплатный уровень и является хорошим выбором для учебных целей.
Когда приведенный выше код будет запущен, у нас будет коллекция laptops
в базе данных products
, содержащая 200 документов данных ноутбука. Документы имеют следующее содержание:
Теперь, когда данные готовы, мы можем начать писать конвейеры агрегации для анализа данных. Рекомендуется использовать MongoDB IDE для написания конвейеров, которые обычно охватывают несколько строк. С помощью IDE вы можете использовать автозаполнение кода и удобно писать запросы или конвейеры, охватывающие несколько строк. Однако в этом посте вы можете просто скопировать код, запустить его внутри mongosh
и там проверить результат.
$group
этап
Давайте сначала проверим, сколько документов у нас есть в коллекции.
$ docker exec -it mongo-server bash $ mongosh "mongodb://admin:pass@localhost:27017" test> use products products> db.laptops.countDocuments() 200
Мы также можем использовать конвейер агрегации для подсчета количества документов, что сейчас кажется излишним, но позже он может послужить введением в более сложные конвейеры:
В нашем первом конвейере есть только один этап $group
, который, как следует из названия, группирует документы по указанным _id
. Обычно мы указываем поле для _id
, чтобы группировать документы по этому полю. Однако, особенно когда _id
равно null
, это означает, что мы сгруппируем все документы вместе. Кроме того, total
— это новое поле, содержащее данные, возвращаемые оператором-аккумулятором $count
, который просто подсчитывает количество документов в каждой группе.
Давайте теперь сгруппируем по брендам и проверим число для каждого бренда:
Как упоминалось выше, на этапе $group
_id
задает поле для группировки входных документов. _id
принимает в качестве значения выражение агрегации, которым могут быть пути к полям, системные переменные, объекты выражений и т. д.
"$brand"
— это путь к полю и должен быть заключен в кавычки. Вы можете удивиться, почему мы должны ставить знак доллара $
перед именем поля. Да, это очень сбивает с толку, когда вы впервые видите это. Вы должны понимать, что _id
принимает выражение, а не простое имя поля. И в выражении, если мы хотим указать поле, мы должны поставить перед ним знак доллара, иначе оно будет рассматриваться как литеральная строка.
Технически "$brand"
— это сокращение для "$$CURRENT.brand"
, где CURRENT
— это системная переменная, которая по умолчанию соответствует текущему документу. Как только вы это узнаете, "$brand"
перестанет быть таким загадочным, не так ли?
$sort
этап
Отсортируем ноутбуки по количеству для марки в порядке убывания:
После этапа $group
добавляется новый этап $sort
. Как мы уже знаем, отфильтрованные или агрегированные документы одного этапа передаются на следующий. В этом случае результаты группировки передаются на этап $sort
. Поля исходных документов сейчас недоступны, доступны только те, что были на предыдущем этапе $group
. Следовательно, мы можем упорядочить по полю total
, которое было сгенерировано на предыдущем этапе $group
. Кроме того, $sort
принимает простую строку для имени поля, поэтому вам не нужно добавлять к ней префикс со знаком доллара.
$match
этап
Мы можем добавить этап $match
для фильтрации входных документов и передачи только тех, которые соответствуют заданным условиям, на следующий этап конвейера. Давайте отфильтруем ноутбуки, которых нет в наличии, и рассчитаем только те, которые еще есть в наличии.
Мы можем использовать этап $match
несколько раз в одном конвейере. Давайте выведем только те бренды, которых больше 10 в наличии:
$unwind
этап
Теперь давайте представим более сложный этап, $unwind
, который деконструирует поле массива из входных документов и выводит документ для каждого элемента исходного массива.
Для такого документа:
{"_id": 1, "name": "John", "scores": [70, 90, 80]}
На этапе $unwind
на поле scores
будут сгенерированы следующие документы:
{"_id": 1, "name": "John", "scores": 70} {"_id": 1, "name": "John", "scores": 90} {"_id": 1, "name": "John", "scores": 80}
Результирующие документы $unwind
из того же массива будут иметь один и тот же первичный ключ _id
. Давайте теперь размотаем поле attributes
документов laptops
, которое представляет собой массив атрибутивных документов:
Да, он раскручивается, как и ожидалось, и каждый новый документ содержит один атрибут исходного массива. Обратите внимание, что этап $unwind
ожидает в качестве входных данных путь к полю, который должен быть указан с префиксом со знаком доллара, аналогичным полю _id
для этапа $group
.
Давайте теперь узнаем максимальный объем памяти для каждой марки ноутбука:
[ { _id: 'HP', max_memory: '8GB' }, { _id: 'Dell', max_memory: '8GB' }, { _id: 'Asus', max_memory: '8GB' }, { _id: 'Apple', max_memory: '8GB' }, { _id: 'Lenovo', max_memory: '8GB' } ]
Оператор $last
получает последнего члена группы. При использовании вместе с этапом $sort
, который упорядочивает документы по указанным полям, мы можем получить максимальное значение поля, отсортированного по возрастанию.
Интересно, что все бренды имеют максимальное значение «8 ГБ». Это связано с тем, что значение для памяти является строковым значением, а «8 ГБ» воспринимается как большее, чем «16 ГБ» или «32 ГБ». Нам нужно преобразовать его в числовое значение для сортировки, что может быть достигнуто с помощью этапа $project
.
$project
этап
Этап $project
передает документы с запрошенными полями на следующий этап конвейера. Указанные поля могут быть существующими полями из входных документов или вновь вычисленными. В этом примере мы хотим передать марку как есть, но преобразовать значение строки памяти в числовое значение (в данном случае целое). Значение строки памяти содержит GB, и нам нужно удалить его, прежде чем его можно будет преобразовать в целое число, что можно сделать с помощью оператора $trim
. Преобразование строки в целое число можно выполнить с помощью оператора $toInt
.
Конечным используемым конвейером будет тот, который показан ниже. Обратите внимание, что этап $sort
также используется дважды, как и этап $match
, представленный выше.
И теперь память отсортирована правильно:
[ { _id: 'HP', max_memory: 32 }, { _id: 'Lenovo', max_memory: 32 }, { _id: 'Dell', max_memory: 16 }, { _id: 'Asus', max_memory: 16 }, { _id: 'Apple', max_memory: 16 } ]
Мы можем продолжить анализ данных с различными комбинациями этапов. Тем не менее, это будет похоже на то, что мы представили. После того, как вы освоите основные и часто используемые этапы конвейеров агрегации, вы сможете выполнять все виды анализа, какие захотите. Рекомендуется написать некоторые пайплайны вручную и проверить вывод каждого этапа, чтобы лучше понять логику разных этапов.