При работе с языком запросов 1С программисты часто сталкиваются с неочевидным поведением системы: когда в запросе используется агрегатная функция СУММА, а исходные данные под условия фильтрации не попадают, результатом выполнения запроса становится не пустая выборка (0 строк), а одна строка, содержащая NULL. Это может приводить к ошибкам при дальнейшей обработке результата в коде, например, при попытке сложения или вывода в отчет. В этой статье мы подробно разберем, почему так происходит, как правильно обрабатывать такие ситуации в запросе и в программном коде, а также затронем вопросы производительности различных подходов.
Для начала проанализируем ситуацию с точки зрения архитектуры СУБД (SQL). Поведение запроса кардинально меняется в зависимости от того, используем мы группировку или нет. Рассмотрим два сценария:
СУММА, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ), но отсутствует секция группировки, стандарт SQL (которому следует и 1С) обязывает систему вернуть ровно одну строку. Если исходное множество данных пусто, агрегатные функции, работающие со значениями, возвращают NULL, так как сумма элементов «ничего» математически не определена. Исключением является функция КОЛИЧЕСТВО, которая в такой ситуации вернет 0.Рассмотрим этот нюанс на примере. Если мы просто считаем обороты по регистру бухгалтерии и данных нет, мы получим строку с NULL. Если же мы добавим группировку по Организации, и оборотов нет ни по одной организации, выборка будет абсолютно пустой.
Выясним наиболее эффективный способ получения нуля вместо NULL непосредственно в тексте запроса. Часто разработчики пытаются использовать функцию ЕСТЬNULL внутри агрегата или снаружи. Рассмотрим правильный синтаксис.
Наиболее корректным и производительным вариантом является «обертывание» всей агрегатной функции в проверку на NULL. Посмотрим на пример кода:
ВЫБРАТЬ
ЕСТЬNULL(СУММА(ХозрасчетныйОбороты.СуммаОборотДт), 0) КАК ВыплатаДолга
ИЗ
РегистрБухгалтерии.Хозрасчетный.Обороты(&НачалоПериода, &КонецПериода, , Счет = &Сч66, , , , ) КАК ХозрасчетныйОбороты
В данном случае, если виртуальная таблица Обороты не вернет ни одной строки, функция СУММА вернет NULL (согласно правилам скалярной агрегации), а внешняя функция ЕСТЬNULL преобразует этот NULL в полноценное число 0.
Давайте разберем по шагам, почему положение функции ЕСТЬNULL критически важно для производительности вашего решения, особенно на больших объемах данных.
ЕСТЬNULL для каждой строки исходной таблицы еще до того, как начнется процесс суммирования. Если в выборке миллион строк, будет произведен миллион проверок. Более того, это может помешать оптимизатору СУБД эффективно использовать индексы. Стоит также помнить, что если выборка будет абсолютно пуста, этот вариант в скалярном запросе все равно вернет NULL, так как агрегатной функции «не над чем» было выполнять суммирование.NULL внутри выборки просто игнорируются при суммировании), и только один раз для финального результата выполняется проверка на NULL. Это значительно экономит ресурсы процессора.Важный нюанс: использование ВЫРАЗИТЬ(... КАК ЧИСЛО) внутри СУММА обычно избыточно, если поле изначально имеет числовой тип в метаданных (как, например, ресурсы регистров).
Иногда нам вообще не нужна строка, если в ней получился NULL. В этом случае мы можем использовать условие ИМЕЮЩИЕ. Проанализируем ситуацию: если мы хотим полностью исключить «пустые» итоги из результата, можно написать так:
ВЫБРАТЬ
СУММА(ХозрасчетныйОбороты.СуммаОборотДт) КАК ВыплатаДолга
ИЗ
РегистрБухгалтерии.Хозрасчетный.Обороты(...) КАК ХозрасчетныйОбороты
ИМЕЮЩИЕ
СУММА(ХозрасчетныйОбороты.СуммаОборотДт) ЕСТЬ НЕ NULL
Такой подход заставит запрос вернуть пустую выборку (0 строк), если данных нет, вместо одной строки с NULL. Однако помните, что если вы в дальнейшем заполняете структуру или коллекцию в коде 1С, отсутствие строки может потребовать дополнительной проверки Выборка.Следующий().
Если по каким-то причинам вы не хотите или не можете менять текст запроса (например, запрос формируется динамически в типовом механизме), проблему можно решить на этапе обхода результата. Рассмотрим пример универсального приведения NULL к нулю при заполнении структуры:
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ЗаполнитьЗначенияСвойств(РезультатСтруктура, Выборка);
// Перебираем свойства структуры для очистки от NULL
Для каждого Элемент Из РезультатСтруктура Цикл
Если Элемент.Значение = NULL Тогда
РезультатСтруктура[Элемент.Ключ] = 0;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Этот метод удобен, когда полей в запросе очень много и прописывать ЕСТЬNULL для каждого поля трудозатратно.
Если вы разрабатываете отчет на СКД, проблему пустых значений можно решить еще элегантнее, не перегружая запрос лишними функциями. Рассмотрим доступные инструменты:
ЕСТЬNULL(Сумма(Поле), 0). Это обеспечит корректный расчет итогов по группировкам и иерархии.Мы проанализировали поведение агрегатных функций в пустых выборках и выяснили, что появление NULL — это не ошибка платформы 1С, а стандартное поведение SQL-совместимых систем. Для правильной работы рекомендуется:
ЕСТЬNULL(СУММА(Поле), 0) как наиболее быструю и надежную.СУММА(ЕСТЬNULL(...)) из-за избыточной нагрузки на сервер базы данных.КОЛИЧЕСТВО() помнить, что эта функция сама по себе возвращает 0 при отсутствии данных в скалярном режиме.Соблюдение этих правил сделает ваши запросы более стабильными, а код — чистым и понятным для коллег.