Каждый разработчик на платформе 1С:Предприятие рано или поздно сталкивается со странным поведением системы при анализе типов колонок в результатах запроса. Например, выбирая совершенно обычное числовое поле из табличной части документа (скажем, поле Сумма из документа РеализацияТоваровУслуг), мы обнаруживаем, что тип этой колонки в таблице значений описывается не просто как Число, а как составной тип NULL, Число. При этом в самом результате запроса может не быть ни одной строчки со значением NULL.
Другая связанная с этим проблема — внезапное исчезновение квалификаторов числа (точности и разрядности) после применения группировок или агрегатных функций вроде СУММА. Давайте подробно разберем, почему так происходит, как устроена логика платформы 1С и СУБД, и как правильно принудительно задавать типы в запросах.
Для того чтобы понять, откуда берется тип NULL, нам нужно заглянуть во внутренние механизмы работы платформы 1С и СУБД. Когда мы выполняем запрос и выгружаем его результат в таблицу значений, платформа формирует схему типов данных (объект ОписаниеТипов) еще до того, как СУБД вернет реальные физические строки с данными.
Платформа идет по пути универсальности и оптимизации:
NULL в конкретном поле, компилятору запросов 1С пришлось бы детально анализировать структуру таблиц, связи, условия соединений и логические конструкции. Вместо этого платформа превентивно добавляет тип NULL во все потенциально вариативные колонки.NULL в результате левого или правого соединения (LEFT JOIN / RIGHT JOIN), при обращении к реквизитам битых ссылок или при использовании логического ветвления ВЫБОР КОГДА ... ТОГДА.NULL в описании типов защищает код встроенного языка от аварийного завершения (ошибок несоответствия типов) при чтении данных из полученной таблицы значений.Интересное исключение составляют строковые реквизиты некоторых табличных частей. Для них СУБД на уровне физической структуры гарантирует свойство NOT NULL, а объектная модель 1С всегда записывает пустую строку "" при отсутствии значения. В этих редких случаях платформа точно "знает", что NULL там возникнуть не может, и не включает его в описание типов.
Часто разработчики замечают, что при выгрузке результата запроса в таблицу значений у числовых колонок сбрасываются свойства Разрядность и РазрядностьДробнойЧасти (они становятся равными нулю/пустыми). Проанализируем, почему это происходит именно при группировке данных.
Это поведение напрямую связано с версией платформы 1С и режимом совместимости:
СУММА() или СРЕДНЕЕ() автоматически наследовал квалификаторы исходного поля метаданных.СУММА() и СРЕДНЕЕ() результирующему полю всегда присваивается тип числа неограниченной длины. Платформа делает это умышленно, чтобы предотвратить возможное переполнение разрядов при суммировании огромных массивов данных (когда сумма множества чисел с точностью 15.2 выходит за рамки этих 15 знаков).Если вам критически важно, чтобы выгруженная таблица значений содержала четко типизированную колонку с фиксированной разрядностью (например, для последующей передачи в другие алгоритмы или для корректного отображения в интерфейсе), необходимо явно указывать желаемый тип данных непосредственно в тексте запроса.
Для этого мы используем конструкцию ВЫРАЗИТЬ(). Давайте посмотрим на практический пример:
ВЫБРАТЬ
Реализация.Ссылка КАК Документ,
ВЫРАЗИТЬ(СУММА(Реализация.Товары.Сумма) КАК ЧИСЛО(15, 2)) КАК ВсегоСумма
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
СГРУППИРОВАТЬ ПО
Реализация.Ссылка
При таком подходе платформа жестко зафиксирует в ОписаниеТипов колонки ВсегоСумма квалификаторы числа: допустимый знак — любой, разрядность — 15, разрядность дробной части — 2.
При работе с агрегатными функциями в запросах крайне важно помнить о разнице в обработке пустых выборок функциями СУММА и КОЛИЧЕСТВО. Это убережет вас от неожиданного появления значений NULL на выходе:
0.NULL, функция СУММА() вернет значение NULL, а не 0.Для решения этой проблемы разработчики часто используют функцию ЕСТЬNULL(). Однако позиционирование этой функции имеет решающее значение. Рассмотрим два варианта:
Вариант 1 (Неверный для пустых таблиц):
// Если запрос не вернет строк, результатом все равно будет NULL!
ВЫБРАТЬ СУММА(ЕСТЬNULL(Таблица.Сумма, 0)) КАК ИтогоСумма
В данном случае функция ЕСТЬNULL защищает нас только от пустых значений внутри существующих строк. Но если сама таблица окажется пустой, агрегатная функция СУММА все равно вернет общий итог NULL.
Вариант 2 (Правильный и надежный):
// Гарантированно вернет 0 в любых обстоятельствах
ВЫБРАТЬ ЕСТЬNULL(СУММА(Таблица.Сумма), 0) КАК ИтогоСумма
Обернув агрегатную функцию снаружи, мы гарантируем, что даже при абсолютно пустой выборке результатом выполнения запроса станет числовой ноль (0), а тип колонки будет корректно обработан системой.