При построении отчёта с промежуточными итогами как отличить строку итога по всем данным от реальной группы, у которой ключ имеет NULL?
Для этого используют функцию GROUPING: она показывает, был ли столбец свернут из-за текущего набора группировки. Значение 1 означает, что NULL появился как обозначение итога, а 0 — что столбец действительно участвовал в группировке, включая настоящую группу со значением NULL.
Отчётам часто нужны одновременно детальные группы, промежуточные итоги и общий итог. Повторение отдельных запросов с последующим объединением усложняет поддержку и может приводить к дополнительным чтениям данных, поэтому SQL предоставляет GROUPING SETS для декларативного задания нескольких уровней агрегации.
В строках итогов агрегируемые столбцы обычно отображаются как NULL. Но NULL может быть и настоящим значением ключа: например, регион или категория могли отсутствовать в исходной записи.
Если различать эти случаи только по самому значению столбца, отчёт может неверно подписать реальную группу как общий итог или, наоборот, принять итоговую строку за данные. Особенно опасно это при последующей обработке результата, экспорте и визуализации.
GROUPING(столбец) анализирует не значение столбца, а структуру текущего набора группировки. Если столбец свернут и по нему строится итог, функция возвращает 1; если столбец входит в данный набор группировки, она возвращает 0.
В наборе (region, product) оба флага равны 0. В наборе (region) product_aggregated равен 1, поэтому строка является итогом по региону. В пустом наборе () оба флага равны 1 — это общий итог.
Реальный NULL в region или product не меняет соответствующий флаг: он остается 0, если столбец участвовал в группировке. Поэтому для классификации строк следует использовать GROUPING, а не проверки вида столбец IS NULL.
Есть важное ограничение: GROUPING SETS и GROUPING поддерживаются не одинаково во всех СУБД, а синтаксис и дополнительные функции могут различаться. Не следует без проверки переносить запрос между диалектами SQL. Для понятного результата обычно добавляют отдельный вычисляемый признак или текстовую метку уровня агрегации.
В отчёте по продажам требовались строки по региону и товару, итоги по региону и общий итог. В исходных данных часть товаров имела NULL в поле категории.
Первый вариант использовал COALESCE(product, 'Итого'). Он был простым, но ошибочным: реальные записи без категории смешивались с итогами по региону.
Второй вариант объединял три отдельных агрегирующих запроса через UNION ALL. Он позволял явно задать уровни, но дублировал выражения, усложнял изменение фильтров и повышал риск несогласованных условий.
Выбран был GROUPING SETS вместе с GROUPING(product) и GROUPING(region). Это сохранило один набор фильтров и одну схему агрегации, а уровень каждой строки определялся независимо от её отображаемых NULL-значений. В результате реальные пропуски и промежуточные итоги корректно различались в отчёте.
Нет. Проверка на NULL определяет только значение результата, но не причину его появления. Один NULL может быть исходным значением ключа, а другой — результатом свертывания столбца в итоговой строке. Для различения причин нужен GROUPING.
Информация о том, был ли NULL исходным или итоговым, еще доступна в отдельном вызове GROUPING, но после замены значения одной текстовой меткой сами случаи визуально смешаются. Без сохранения признака агрегации последующие потребители результата уже не смогут надежно восстановить уровень строки.
Потому что разные комбинации флагов обозначают разные уровни отчёта. Например, (0, 0) может быть детальной группой, (0, 1) — итогом по региону, а (1, 1) — общим итогом. Проверка только одного столбца может не отличить промежуточный итог от другого уровня агрегации.