Почему все строки с NULL попадают в одну группу при группировке по nullable-столбцу, хотя NULL не равен NULL?
При группировке GROUP BY значения NULL считаются принадлежащими одной группе. Это не означает, что SQL считает NULL = NULL истинным: обычное сравнение с NULL даёт UNKNOWN, а группировка использует собственные правила формирования групп.
Реляционная модель опирается на значения атрибутов и операции над множествами, но практический SQL должен работать с неполными или неизвестными данными. Для этого SQL поддерживает NULL и трёхзначную логику, однако аналитические операции, включая группировку, требуют детерминированного разбиения строк на группы.
Если бы каждая строка с NULL считалась отдельной группой, агрегатные отчёты по отсутствующим значениям были бы непредсказуемыми и малополезными. Поэтому группировка объединяет NULL-значения в одну группу, хотя предикаты сравнения обрабатывают их иначе.
Разработчик может ошибочно перенести правило сравнения NULL в правила группировки и ожидать, что строки с NULL не объединятся. Это приводит к неверной интерпретации отчётов: например, все заказы без указанного региона фактически образуют одну группу «регион не задан».
Важно также не путать группу NULL с группой пустой строки или конкретным текстовым значением. NULL, пустая строка и значение вроде «Не указан» — разные значения и могут формировать разные группы.
Для каждой строки оператор GROUP BY вычисляет ключ группировки. Строки с одинаковыми значениями ключей попадают в одну группу; все строки, у которых ключ равен NULL, относятся к одной NULL-группе.
При этом условие сравнения и группировка используют разные семантические операции. В предикате NULL = NULL результатом является UNKNOWN, тогда как при построении групп SQL обязан классифицировать строки и поэтому рассматривает NULL-ключи как одну категорию.
В результате будет отдельная строка для каждого региона и отдельная строка, где region имеет значение NULL. COUNT(*) посчитает все строки этой группы, включая строки с NULL в region.
Группировка по нескольким столбцам работает аналогично: строки с одинаковыми ненулевыми компонентами и NULL в одних и тех же компонентах попадут в одну группу. Однако NULL не становится обычным значением: он по-прежнему не сравнивается как равный NULL в обычных предикатах.
В отчёте по продажам нужно показать количество заказов по региону клиента. Часть клиентов ещё не прошла классификацию, поэтому region равен NULL.
Вариант с группировкой напрямую по region сохраняет информацию о неполных данных и показывает отдельную группу NULL. Его минус — пользователю может быть непонятна причина отсутствия региона.
Вариант с предварительной заменой NULL на подпись «Регион не указан» улучшает читаемость отчёта, но смешивает представление данных с их хранением. Такой вариант оправдан на уровне отчёта, если подпись явно обозначает отсутствие значения и не совпадает с допустимым реальным регионом.
Практически выбирают группировку по исходному столбцу с отображением NULL как «Регион не указан» только в презентационном слое. В результате статистика сохраняет одну корректную группу для всех неклассифицированных клиентов, а смысл значения остаётся явным.
1. Исключается ли NULL-группа условием WHERE?
Да, если условие не допускает NULL. Например, фильтр по конкретному региону оставит только строки, для которых предикат дал TRUE; строки с NULL обычно дадут UNKNOWN и будут отброшены ещё до группировки. Если NULL-группу нужно сохранить, условие должно явно учитывать отсутствие значения.
2. Что произойдёт с NULL-группой при HAVING?
Она обрабатывается как обычная группа: агрегат вычисляется по её строкам, после чего проверяется условие HAVING. Например, группа с NULL будет удалена, если её количество не удовлетворяет порогу, но сам факт NULL не делает её особой для механизма HAVING.
3. Одинакова ли группировка по NULL в SQL и в реляционной алгебре?
Нельзя безоговорочно считать их одинаковыми. Классическая реляционная алгебра обычно описывает отношения без NULL и трёхзначной логики, тогда как SQL расширяет модель специальной семантикой неполных данных. Поэтому поведение SQL-группировки по NULL — часть семантики SQL, а не прямое следствие классического равенства в реляционной алгебре.