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

Этот пост изначально был опубликован на 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