Почему не работают формулы в вычисляемых полях СКД и как обработать пустые значения?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке отчетов на базе системы компоновки данных (СКД) разработчики часто сталкиваются с ситуацией: в вычисляемом поле прописана простая арифметическая формула вида Значение1 - Значение2, однако результат расчета оказывается пустым. При этом в исходных колонках часть данных заполнена числами, а часть остается пустой. Разберем детально, почему так происходит, какое именно пустое значение возвращает запрос платформы 1С и как правильно решить данную проблему.

Причина сбоя формулы: математика с участием NULL

Проанализируем поведение системы при выполнении арифметических выражений. В языке запросов 1С и на уровне языка выражений СКД действует строгое правило реляционных баз данных: любая арифметическая операция со значением NULL дает в результате NULL.

Если в выражении участвуют четыре поля, например Поле1 + Поле2 + Поле3 + Поле4, и хотя бы в одном из них содержится NULL, вся сумма автоматически превратится в NULL. Пользователь видит в отчете пустое место и нередко ошибочно предполагает, что в ячейку попало значение типа Неопределено.

Выясним принципиальную разницу между этими понятиями в платформе 1С:

  1. NULL — это специальный маркер отсутствия данных в базе данных. Он возникает, когда при соединении таблиц в правой таблице нет соответствующей записи, либо когда происходит неявное разыменование поля.
  2. Неопределено — это специальный тип данных платформы 1С. В таблицах СУБД для реквизитов документов или регистров он не хранится в чистом виде (за исключением внешних наборов данных СКД или объектов типа ТаблицаЗначений, сформированных программно).

Откуда берется NULL в запросе: скрытые соединения

Посмотрим на типовой запрос, вызывающий подобное поведение:


ВЫБРАТЬ
    БезналичнаяОплата.ТорговаяТочка КАК ТорговаяТочка,
    БезналичнаяОплата.Сумма КАК Сумма,
    БезналичнаяОплата.Период КАК Период,
    БезналичнаяОплата.Регистратор КАК Регистратор,
    БезналичнаяОплата.Регистратор.ВведенНаОсновании КАК Основание,
    БезналичнаяОплата.Регистратор.ВведенНаОсновании.Итог КАК Реализация,
    БезналичнаяОплата.Регистратор.ВведенНаОсновании.СтоимостьРеализации КАК Залог
ИЗ
    РегистрНакопления.БезналичнаяОплата КАК БезналичнаяОплата

Обратим внимание на цепочку обращений через точку: БезналичнаяОплата.Регистратор.ВведенНаОсновании.Итог. Поле Регистратор всегда имеет составной тип, а реквизит ВведенНаОсновании чаще всего также является составным ссылочным типом. Когда запрос обращается через точку к реквизиту составного ссылочного поля, платформа выполняет неявные левые соединения (LEFT JOIN) со всеми возможными таблицами документов из этого типа.

Если у конкретного типа документа нет реквизита Итог или документ-основание вовсе не заполнен, СУБД возвращает NULL. В результате в колонку Реализация попадает именно NULL, что и ломает формулу в СКД.

Способ 1. Замена NULL на ноль в тексте запроса с помощью ЕСТЬNULL

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

Скорректируем блок выборки числовых полей:


ВЫБРАТЬ
    БезналичнаяОплата.ТорговаяТочка КАК ТорговаяТочка,
    БезналичнаяОплата.Сумма КАК Сумма,
    БезналичнаяОплата.Период КАК Период,
    БезналичнаяОплата.Регистратор КАК Регистратор,
    БезналичнаяОплата.Регистратор.ВведенНаОсновании КАК Основание,
    ЕСТЬNULL(БезналичнаяОплата.Регистратор.ВведенНаОсновании.Итог, 0) КАК Реализация,
    ЕСТЬNULL(БезналичнаяОплата.Регистратор.ВведенНаОсновании.СтоимостьРеализации, 0) КАК Залог
ИЗ
    РегистрНакопления.БезналичнаяОплата КАК БезналичнаяОплата

Теперь вместо отсутствующих значений в результат запроса всегда возвращаются полноценные нули, и вычисляемое поле в СКД отработает корректно.

Способ 2. Обработка пустых значений на уровне выражений СКД

Если запрос по каким-то причинам менять нельзя (например, используется типовой отчет или закрытый источник), обработку пустых значений можно выполнить непосредственно на вкладке Вычисляемые поля в схеме компоновки данных.

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

ЕстьNULL(Реализация, 0) - ЕстьNULL(Залог, 0)

Если же данные поступают из внешнего набора (например, из таблицы значений), где в колонках может действительно содержаться значение Неопределено, используем конструкцию выбора:


Выбор 
    Когда Реализация = Неопределено Или Реализация Есть NULL Тогда 0 
    Иначе Реализация 
Конец 
- 
Выбор 
    Когда Залог = Неопределено Или Залог Есть NULL Тогда 0 
    Иначе Залог 
Конец

Способ 3. Оптимизация запроса: отказ от неявных соединений

Рассмотренный выше вариант с обращением через точку Регистратор.ВведенНаОсновании.Итог решает проблему с расчетом, но несет серьезную угрозу производительности. Многократное разыменование составных типов грубо нарушает стандарты разработки 1С. Каждая точка на составном поле порождает каскад соединений со всеми таблицами метаданных, входящими в тип, что многократно замедляет выполнение запроса на больших объемах данных.

Разберем, как переписать этот запрос правильно. Для этого применим явное ограничение типа с помощью функции ВЫРАЗИТЬ либо явные левые соединения с конкретными таблицами документов.

Посмотрим на пример с явными соединениями:


ВЫБРАТЬ
    БезналичнаяОплата.ТорговаяТочка КАК ТорговаяТочка,
    БезналичнаяОплата.Сумма КАК Сумма,
    БезналичнаяОплата.Период КАК Период,
    БезналичнаяОплата.Регистратор КАК Регистратор,
    ЕСТЬNULL(Реализация.Итог, 0) КАК Реализация,
    ЕСТЬNULL(Залог.СтоимостьРеализации, 0) КАК Залог
ИЗ
    РегистрНакопления.БезналичнаяОплата КАК БезналичнаяОплата
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
        ПО БезналичнаяОплата.Регистратор = Реализация.Ссылка
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗалоговыйБилет КАК Залог
        ПО БезналичнаяОплата.Регистратор = Залог.Ссылка

Если же связь идет через реквизит основания, ограничим тип документа перед обращением:


ВЫБРАТЬ
    ЕСТЬNULL(ВЫРАЗИТЬ(БезналичнаяОплата.Регистратор.ВведенНаОсновании КАК Документ.РеализацияТоваровУслуг).Итог, 0) КАК Реализация,
    ЕСТЬNULL(ВЫРАЗИТЬ(БезналичнаяОплата.Регистратор.ВведенНаОсновании КАК Документ.ЗалоговыйБилет).СтоимостьРеализации, 0) КАК Залог
ИЗ
    РегистрНакопления.БезналичнаяОплата КАК БезналичнаяОплата

Использование конструкции ВЫРАЗИТЬ(Поле КАК Тип) четко указывает СУБД, с какой конкретно таблицей нужно соединяться, исключая лишние соединения, а функция ЕСТЬNULL гарантирует целостность числовых данных для последующих расчетов в СКД.

← На главную