Почему агрегатная функция СУММА() возвращает NULL в пустой выборке и как это исправить?

Программист 1С v8.3 (Обычные формы) Бухгалтерский учет
← На главную

При работе с языком запросов 1С программисты часто сталкиваются с неочевидным поведением системы: когда в запросе используется агрегатная функция СУММА, а исходные данные под условия фильтрации не попадают, результатом выполнения запроса становится не пустая выборка (0 строк), а одна строка, содержащая NULL. Это может приводить к ошибкам при дальнейшей обработке результата в коде, например, при попытке сложения или вывода в отчет. В этой статье мы подробно разберем, почему так происходит, как правильно обрабатывать такие ситуации в запросе и в программном коде, а также затронем вопросы производительности различных подходов.

Причина поведения: Скалярная vs Векторная агрегация

Для начала проанализируем ситуацию с точки зрения архитектуры СУБД (SQL). Поведение запроса кардинально меняется в зависимости от того, используем мы группировку или нет. Рассмотрим два сценария:

  1. Скалярная агрегация (без СГРУППИРОВАТЬ ПО): Если в тексте запроса присутствует агрегатная функция (СУММА, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ), но отсутствует секция группировки, стандарт SQL (которому следует и 1С) обязывает систему вернуть ровно одну строку. Если исходное множество данных пусто, агрегатные функции, работающие со значениями, возвращают NULL, так как сумма элементов «ничего» математически не определена. Исключением является функция КОЛИЧЕСТВО, которая в такой ситуации вернет 0.
  2. Векторная агрегация (с СГРУППИРОВАТЬ ПО): Если мы добавим в запрос хотя бы одно поле группировки, поведение изменится. При отсутствии исходных данных СУБД вернет пустой результат (0 строк). Это происходит потому, что группировке «не за что зацепиться» — нет ни одного значения, вокруг которого можно было бы собрать группу.

Рассмотрим этот нюанс на примере. Если мы просто считаем обороты по регистру бухгалтерии и данных нет, мы получим строку с NULL. Если же мы добавим группировку по Организации, и оборотов нет ни по одной организации, выборка будет абсолютно пустой.

Как привести NULL к числу в запросе

Выясним наиболее эффективный способ получения нуля вместо NULL непосредственно в тексте запроса. Часто разработчики пытаются использовать функцию ЕСТЬNULL внутри агрегата или снаружи. Рассмотрим правильный синтаксис.

Наиболее корректным и производительным вариантом является «обертывание» всей агрегатной функции в проверку на NULL. Посмотрим на пример кода:


ВЫБРАТЬ
    ЕСТЬNULL(СУММА(ХозрасчетныйОбороты.СуммаОборотДт), 0) КАК ВыплатаДолга
ИЗ
    РегистрБухгалтерии.Хозрасчетный.Обороты(&НачалоПериода, &КонецПериода, , Счет = &Сч66, , , , ) КАК ХозрасчетныйОбороты

В данном случае, если виртуальная таблица Обороты не вернет ни одной строки, функция СУММА вернет NULL (согласно правилам скалярной агрегации), а внешняя функция ЕСТЬNULL преобразует этот NULL в полноценное число 0.

Производительность: СУММА(ЕСТЬNULL) или ЕСТЬNULL(СУММА)?

Давайте разберем по шагам, почему положение функции ЕСТЬNULL критически важно для производительности вашего решения, особенно на больших объемах данных.

  1. Вариант СУММА(ЕСТЬNULL(Поле, 0)): В этом сценарии СУБД вынуждена выполнять проверку ЕСТЬNULL для каждой строки исходной таблицы еще до того, как начнется процесс суммирования. Если в выборке миллион строк, будет произведен миллион проверок. Более того, это может помешать оптимизатору СУБД эффективно использовать индексы. Стоит также помнить, что если выборка будет абсолютно пуста, этот вариант в скалярном запросе все равно вернет NULL, так как агрегатной функции «не над чем» было выполнять суммирование.
  2. Вариант ЕСТЬNULL(СУММА(Поле), 0): Это оптимальный путь. СУБД сначала выполняет суммирование всех строк (согласно стандарту SQL, NULL внутри выборки просто игнорируются при суммировании), и только один раз для финального результата выполняется проверка на NULL. Это значительно экономит ресурсы процессора.

Важный нюанс: использование ВЫРАЗИТЬ(... КАК ЧИСЛО) внутри СУММА обычно избыточно, если поле изначально имеет числовой тип в метаданных (как, например, ресурсы регистров).

Использование конструкции ИМЕЮЩИЕ

Иногда нам вообще не нужна строка, если в ней получился NULL. В этом случае мы можем использовать условие ИМЕЮЩИЕ. Проанализируем ситуацию: если мы хотим полностью исключить «пустые» итоги из результата, можно написать так:


ВЫБРАТЬ
    СУММА(ХозрасчетныйОбороты.СуммаОборотДт) КАК ВыплатаДолга
ИЗ
    РегистрБухгалтерии.Хозрасчетный.Обороты(...) КАК ХозрасчетныйОбороты
ИМЕЮЩИЕ
    СУММА(ХозрасчетныйОбороты.СуммаОборотДт) ЕСТЬ НЕ NULL

Такой подход заставит запрос вернуть пустую выборку (0 строк), если данных нет, вместо одной строки с NULL. Однако помните, что если вы в дальнейшем заполняете структуру или коллекцию в коде 1С, отсутствие строки может потребовать дополнительной проверки Выборка.Следующий().

Обработка результата в коде 1С

Если по каким-то причинам вы не хотите или не можете менять текст запроса (например, запрос формируется динамически в типовом механизме), проблему можно решить на этапе обхода результата. Рассмотрим пример универсального приведения NULL к нулю при заполнении структуры:


Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    ЗаполнитьЗначенияСвойств(РезультатСтруктура, Выборка);
    
    // Перебираем свойства структуры для очистки от NULL
    Для каждого Элемент Из РезультатСтруктура Цикл
        Если Элемент.Значение = NULL Тогда
            РезультатСтруктура[Элемент.Ключ] = 0;
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

Этот метод удобен, когда полей в запросе очень много и прописывать ЕСТЬNULL для каждого поля трудозатратно.

Особенности Системы компоновки данных (СКД)

Если вы разрабатываете отчет на СКД, проблему пустых значений можно решить еще элегантнее, не перегружая запрос лишними функциями. Рассмотрим доступные инструменты:

Подведем итоги

Мы проанализировали поведение агрегатных функций в пустых выборках и выяснили, что появление NULL — это не ошибка платформы 1С, а стандартное поведение SQL-совместимых систем. Для правильной работы рекомендуется:

  1. Использовать конструкцию ЕСТЬNULL(СУММА(Поле), 0) как наиболее быструю и надежную.
  2. Понимать разницу между скалярным запросом (всегда 1 строка) и запросом с группировкой (0 строк при отсутствии данных).
  3. Избегать СУММА(ЕСТЬNULL(...)) из-за избыточной нагрузки на сервер базы данных.
  4. При использовании КОЛИЧЕСТВО() помнить, что эта функция сама по себе возвращает 0 при отсутствии данных в скалярном режиме.

Соблюдение этих правил сделает ваши запросы более стабильными, а код — чистым и понятным для коллег.

← На главную