При разработке запросов в системе 1С:Предприятие программисты часто сталкиваются с ситуацией, когда результат выполнения запроса кажется нелогичным. Одной из таких классических проблем является получение строки со значением NULL вместо ожидаемого пустого результата (0 строк) при использовании агрегатных функций в таблицах, где нет данных. Рассмотрим эту ситуацию подробно, разберем причины такого поведения и изучим способы корректного решения задачи.
Представим стандартную ситуацию: мы создаем временную таблицу, накладываем на нее фильтр, который отсекает все записи (делает таблицу пустой), а затем пытаемся посчитать сумму по какому-либо полю. Проанализируем пример кода:
// Шаг 1: Создаем ВТ с данными
ВЫБРАТЬ
"А" КАК Поле,
100 КАК Значение
ПОМЕСТИТЬ ВременнаяТаблица
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Б",
200
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Создаем пустую ВТ (условие ГДЕ заведомо ложное)
ВЫБРАТЬ
ВременнаяТаблица.Поле КАК Поле,
ВременнаяТаблица.Значение КАК Значение
ПОМЕСТИТЬ ВременнаяТаблицаСОтбором
ИЗ
ВременнаяТаблица КАК ВременнаяТаблица
ГДЕ
ВременнаяТаблица.Поле = "НесуществующееЗначение"
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Агрегация
ВЫБРАТЬ
СУММА(ВременнаяТаблицаСОтбором.Значение) КАК Значение
ИЗ
ВременнаяТаблицаСОтбором КАК ВременнаяТаблицаСОтбором
Казалось бы, если в таблице ВременнаяТаблицаСОтбором нет ни одной записи, то и итоговый запрос должен вернуть пустой результат. Однако на практике запрос возвращает одну строку, в которой значение поля Значение равно NULL. Для разработчика, ожидающего, что метод Запрос.Выполнить().Пустой() вернет значение Истина, это может стать причиной ошибки в логике программы. При необходимости детального анализа таких ситуаций можно использовать инструмент для отладки запросов с выгрузкой временных таблиц.
Для понимания причин такого поведения важно различать два режима работы агрегатных функций в языке запросов SQL, на котором базируется платформа 1С:
ВЫБРАТЬ СУММА(Поле) без указания полей группировки, система рассматривает весь результирующий набор данных как одну-единственную группу. Даже если исходная таблица пуста, эта «виртуальная» группа считается существующей. Поскольку функция СУММА не находит данных для расчета, она возвращает NULL (неопределенное значение), но сама строка результата при этом создается.Рассмотрим пример с группировкой, который вернет пустой результат:
ВЫБРАТЬ
ВременнаяТаблицаСОтбором.Поле КАК Поле,
СУММА(ВременнаяТаблицаСОтбором.Значение) КАК Значение
ИЗ
ВременнаяТаблицаСОтбором КАК ВременнаяТаблицаСОтбором
СГРУППИРОВАТЬ ПО
ВременнаяТаблицаСОтбором.Поле
В данном случае, если исходная таблица пуста, запрос не вернет ни одной строки, что часто является более ожидаемым поведением для бизнес-логики.
Интересно проанализировать ситуацию с другими агрегатными функциями. Выясним причину, по которой КОЛИЧЕСТВО ведет себя иначе. Согласно математической логике и стандартам SQL:
СУММА, СРЕДНЕЕ, МИНИМУМ, МАКСИМУМ возвращают NULL на пустом множестве, так как результат операции не определен.КОЛИЧЕСТВО (или COUNT) на пустом множестве всегда возвращает 0. Это происходит потому, что количество элементов в пустом наборе данных — это конкретное числовое значение.Тем не менее, даже если запрос с КОЛИЧЕСТВО(*) вернет 0, это все равно будет одна строка с нулем, а не отсутствие строк.
Существует несколько проверенных подходов, позволяющих избежать проблем с NULL в результатах запросов.
Это самый распространенный и рекомендуемый метод. Мы принудительно заменяем неопределенное значение на числовой ноль. Это не уберет "лишнюю" строку из результата, но предотвратит ошибки при дальнейших арифметических операциях в коде 1С.
ВЫБРАТЬ
ЕСТЬNULL(СУММА(ВТ.Значение), 0) КАК Значение
ИЗ
ВТ
Если нам критично, чтобы запрос вообще не возвращал строк, если данных нет, мы можем использовать условие ИМЕЮЩИЕ. Проанализируем пример:
ВЫБРАТЬ
СУММА(ВТ.Значение) КАК Значение
ИЗ
ВТ
ИМЕЮЩИЕ
КОЛИЧЕСТВО(*) > 0
В этом случае условие КОЛИЧЕСТВО(*) > 0 отсечет ту самую единственную строку с NULL, так как фактически записей в выборке нет.
Важно помнить, что поведение механизмов расчета итогов (предложение ИТОГИ) отличается от простых агрегатных функций. Если в запросе присутствуют ИТОГИ, но нет детальных записей, удовлетворяющих условиям ГДЕ, результат запроса будет абсолютно пустым, включая строку общих итогов.
Также стоит учитывать поведение при соединениях таблиц. Например, при использовании ЛЕВОЕ СОЕДИНЕНИЕ с пустой таблицей, количество строк в результате будет соответствовать левой таблице, а все поля правой таблицы будут заполнены NULL. Однако если мы используем агрегацию по полям правой таблицы, мы снова можем столкнуться с описанным выше эффектом глобальной группировки. Для лучшего понимания работы подобных механизмов можно воспользоваться материалом с объяснением работы соединений в запросах для начинающих.
Подведем итоги нашего анализа:
СГРУППИРОВАТЬ ПО всегда возвращает минимум одну строку, даже если данных нет.СУММА в этом случае результатом будет NULL, для КОЛИЧЕСТВО — 0.СГРУППИРОВАТЬ ПО или условие ИМЕЮЩИЕ.Null, если используете глобальную агрегацию, или заранее применяйте ЕСТЬNULL в тексте запроса.Понимание этих нюансов работы движка запросов позволяет писать более надежный код и избегать трудноуловимых ошибок при расчете итоговых показателей в отчетах и документах.