Ваша схема данных настолько гибкая, что не может соответствовать каждому варианту использования. Получайте индивидуальную аналитику из ваших данных с помощью группы и других возможностей
Этот пост изначально был опубликован на rrawat.com.
Групповой этап в конвейере агрегации MongoDB помогает нам группировать данные, используя любое поле в документе MongoDB. Это один из наиболее важных и часто используемых этапов конвейера агрегации MongoDB.
В этой статье мы рассмотрим этап $group
и используем различные возможности, которые он предоставляет. Мы будем работать с пробной коллекцией фильмов. Для каждого запроса будет ссылка на игровую площадку, чтобы вы могли практиковаться и учиться на практике.
В конце этой статьи также есть упражнение, которое вы можете попробовать. Это поможет вам укрепить свое понимание после прочтения этой статьи.
Установить данные
Прежде чем мы перейдем к конвейеру агрегации и групповому этапу, нам нужны некоторые данные для работы. Я беру пример коллекции Movies
для понимания концепции. Опять же, в статье будут ссылки на игровую площадку для каждого запроса.
Вот коллекция Movies
, содержащая всего 5 документов, содержащих случайные данные:
Теперь, когда у нас есть коллекция образцов, пришло время изучить этап $group
.
Найдите отдельные записи, используя группу по
Чтобы найти отдельные элементы в коллекции, мы можем использовать групповой этап для любого поля, по которому мы хотим сгруппировать. Это поле будет уникальным в выводе. Давайте сгруппируем фильмы по году выпуска:
Вот результат вышеуказанного запроса. Обратите внимание, что в выходных данных мы получили только уникальные значения года выпуска.
Группировка с использованием нескольких полей
Подобно группировке по одному полю, мы можем захотеть сгруппировать данные с более чем одним полем в соответствии с нашим вариантом использования. Конвейер агрегации MongoDB позволяет нам группировать по любому количеству полей.
Все, что мы помещаем в поле _id
, используется для группировки документов, т. е. возвращает все поля, присутствующие в поле _id
, и группируется по всем из них.
Давайте сгруппируем фильмы по году выпуска и времени показа:
Группировка по году выпуска и времени их выполнения дает нам следующий результат:
Вместо использования одного поля для группировки мы используем несколько полей в приведенном выше сценарии. Сочетание года выпуска и времени выполнения выступает в качестве уникального идентификатора для каждого документа.
Аккумуляторные функции
На групповом этапе доступно множество функций-аккумуляторов, которые можно использовать для агрегирования данных. Они помогают нам выполнять некоторые из наиболее распространенных операций над сгруппированными данными. Давайте посмотрим на некоторые из них:
Накопитель $count
Используется для подсчета количества документов в группе. Это можно объединить с нашим групповым запросом, чтобы получить общее количество документов в группе.
Применим это к нашей коллекции фильмов:
Мы получим общее количество фильмов, выпущенных каждый год:
Аккумулятор $sum
Мы можем использовать аккумулятор $sum
, чтобы сложить все значения в поле. Давайте сгруппируем фильмы по их рейтингу и просуммируем отзывы, чтобы понять, есть ли связь между рейтингом фильма и количеством отзывов.
И здесь мы видим небольшую корреляцию между количеством отзывов и рейтингом фильма. Они кажутся примерно прямо пропорциональными для нашей коллекции образцов фильмов:
Аккумулятор $avg
Мы могли бы захотеть изучить, какой год имеет самый высокий средний рейтинг фильмов для аналитических целей. Давайте посмотрим, как мы можем получить эту статистику из наших данных используя $avg
аккумулятор:
Сначала мы группируем фильмы по году выпуска, а затем вычисляем средний рейтинг для каждого года выпуска. Вот результат вышеуказанного запроса:
аккумулятор $push
Мы хотим просмотреть все рейтинги для каждого года выпуска. Мы можем использовать эти данные, чтобы проанализировать, в каком году рейтинги фильмов сильно различались. Для этого задания воспользуемся аккумулятором$push
:
Все рейтинги фильмов для каждого года выпуска помещаются в массив:
Накопитель $addToSet
Вы можете рассматривать это как аккумулятор $push
. $addToSet
добавляет значение в массив только в том случае, если оно еще не существует. Это единственная разница между двумя аккумуляторами.
Давайте сгруппируем фильмы по их рейтингу и соберем все уникальные годы выпуска по каждому рейтингу:
Мы получаем рейтинги вместе с их уникальными годами выпуска:
Аккумулятор $min
Допустим, мы хотим узнать успешные годы выпуска фильмов. Год считается успешным, если все фильмы, выпущенные в этом году, имеют рейтинг больше 7. Воспользуемся аккумулятором $min
, чтобы получить успешные годы:
- Мы сгруппировали коллекцию фильмов с помощью поля
release_year
. - Поле
minRating
поддерживает минимальный рейтинг для каждого года выпуска. - Наконец, этап
$match
для фильтрации лет, минимальный рейтинг которых не превышает 7.
$первый аккумулятор
Этот аккумулятор немного отличается от оператора массива $first
, который возвращает первый элемент массива. Для каждого сгруппированного документа $first
аккумулятор дает нам первый.
Давайте выберем фильм с самым высоким рейтингом за каждый год выпуска. Поскольку мы хотим получить документ с наивысшим рейтингом из каждой группы, нам нужно отсортировать документы перед передачей их на групповой этап.
Здесь мы сортируем по двум полям: release_year
и rating
. Давайте сначала разберемся с выходом этапа $sort
:
Выходные данные сначала сортируются по возрастанию года выпуска, а затем для каждого года фильмы сортируются в порядке убывания рейтинга.
Этот отсортированный вывод затем передается на этап $group
, на котором документы группируются по году их выпуска. Например, этап $group
работает с двумя документами 2005 года выпуска:
Давайте назовем эти отобранные документы для 2005 года выпуска. Это происходит для всех уникальных годов выпуска. На этапе $group
из этих отобранных документов выбирается первый элемент (имеющий наивысший рейтинг, поскольку рейтинги отсортированы в порядке убывания).
Объединив этапы $sort
и $group
, вот окончательный результат запроса:
ПРИМЕЧАНИЕ. Передача отсортированных документов на этап
$group
не гарантирует сохранения порядка.
Объединить $group stage с $project
Рейтинг фильма представляет собой число с плавающей запятой. Мы округлим это число до ближайшего целого числа, чтобы получить рейтинг фильма в виде целого числа. Наконец, мы сгруппируем фильмы по измененным рейтингам.
Запрос выдает нам количество фильмов с тем или иным (модифицированным) рейтингом:
- Мы использовали этап
$project
, чтобы округлить рейтинг до ближайшего целого числа. - Затем этап
$group
для группировки фильмов по измененному рейтингу.
Вот результат вышеуказанного запроса:
Возможности безграничны. Вы можете комбинировать множество других этапов, выполнять некоторые фильтры, ставить условия или даже $$REMOVE
документы.
Отсортируйте результаты с помощью $sort
Год с наибольшим количеством минут в фильмах может дать нам некоторое представление о производстве фильмов и его корреляции с охватом внимания аудитории за эти годы. Итак, давайте разберемся, как этого добиться с помощью следующего запроса:
Мы получаем общую продолжительность всех фильмов, выпущенных в определенном году, а затем сортируем их в порядке убывания с помощью этапа $sort
:
Из этого запроса видно, что объем внимания целевой аудитории с годами снижается неравномерно.
$группа против стадии $проекта
У нас есть отношение n:1 между входными и выходными документами на этапе $group
. Но у нас соотношение 1:1 на стадии $project
.
На этапе $group
мы обычно получаем количество, сумму, среднее число документов на основе ключа группировки (или _id
) или даже строим массив. Все эти операции требуют n документов, а выходом группы является один документ с агрегированными значениями.
С другой стороны, мы включаем/исключаем поля, выполняем преобразования полей в рамках одного документа в случае стадии проекта в конвейере агрегации:
На что следует обратить внимание
На $group stage установлено ограничение в 100 мегабайт ОЗУ
Если вы работаете с массивным набором данных и получаете сообщение об ошибке во время выполнения группового этапа, возможно, вы исчерпали лимит памяти. Если вы хотите увеличить его, используйте параметр allowDiskUse
, чтобы включить этап $group
для записи во временные файлы на диске.
Причина этой проблемы очень хорошо указана в официальной документации MongoDB:
«Этапы конвейера работают с потоками документов, при этом каждый этап конвейера принимает документы, обрабатывает их, а затем выводит результирующие документы.
Некоторые этапы не могут выводить никакие документы, пока они не обработают все входящие документы. Эти этапы конвейера должны хранить выходные данные своего этапа в ОЗУ до тех пор, пока не будут обработаны все входящие документы. В результате для этих этапов конвейера может потребоваться больше места, чем ограничение в 100 МБ». — Документы MongoDB
Заключение
Вот и все! Это было введением в то, как работает групповой этап в конвейере агрегации MongoDB. Мы рассмотрели, как мы можем группировать данные, используя отдельные поля (различное количество), несколько полей, сортировать их, как мы можем выполнять сложные вычисления, добавляя условия на этап $group
, а также тонкую разницу между этапами $group
и $project
.
Наконец, я хотел бы поблагодарить вас за то, что вы нашли время, чтобы прочитать и понять концепцию. Я надеюсь, что это поможет углубить ваше понимание того, как работает групповая агрегация в MongoDB.
Want to Connect? Here's an excercise To solidify your understanding, I have curated a couple of questions related to what we’ve learned in this article. You can download the exercise PDF below. It contains MongoDB playground links containing the answers to all the questions: 5 quick questions on the group stage with answers