Механизм дополнительных реквизитов и сведений — это мощный инструмент в 1С:Предприятии 8, позволяющий пользователям динамически расширять свойства объектов (справочников, документов) без изменения конфигурации. Однако при разработке отчетов и написании запросов программисты часто сталкиваются с проблемой: СУБД отказывается суммировать значения этих реквизитов, выдавая ошибки несовместимости типов или некорректного использования агрегатных функций. В этой статье мы подробно разберем, почему возникает эта проблема, и рассмотрим эффективные способы ее решения.
Для начала проанализируем структуру хранения данных дополнительных реквизитов. В типовых конфигурациях (таких как ERP, Комплексная автоматизация, Управление торговлей или УНФ) дополнительные реквизиты хранятся в табличной части объекта (например, Справочник.Номенклатура.ДополнительныеРеквизиты). Эта табличная часть содержит реквизиты Свойство (ссылка на план видов характеристик) и Значение.
Поле Значение на уровне метаданных имеет составной тип данных. Оно может содержать:
Даже если для конкретного свойства (например, «Условные единицы» или «Объем в литрах») в пользовательском режиме вы жестко задали тип «Число», для СУБД колонка Значение все равно остается составного типа. СУБД (будь то Microsoft SQL Server, PostgreSQL или любая другая) физически не может применить агрегатную функцию СУММА к полю составного типа, так как теоретически там могут находиться строки или ссылки. Это ограничение платформы 1С и реляционных баз данных приводит к ошибкам при компиляции или выполнении запроса.
Многие разработчики замечают, что при выводе тех же данных через Систему компоновки данных (СКД) суммирование полей дополнительных реквизитов в ресурсах работает корректно и без дополнительных манипуляций. Почему так происходит?
Дело в том, что СКД — это интеллектуальный генератор запросов. Когда вы указываете поле в качестве ресурса с функцией СУММА, СКД анализирует схему компоновки данных, определяет реальный тип значения и на уровне генерации итогового SQL-запроса автоматически подставляет конструкции явного приведения типов (аналог SQL-функции CAST). Обычный же запрос (создаваемый через объект Новый Запрос в коде) транслируется в SQL практически «как есть», перекладывая всю ответственность за типизацию на разработчика.
Рассмотрим, как решить эту проблему в обычном запросе по шагам.
Самый надежный и правильный способ заставить СУБД просуммировать значение дополнительного реквизита — это жестко ограничить его тип в самом запросе до числового. Для этого используется встроенная функция языка запросов 1С — ВЫРАЗИТЬ().
Рассмотрим синтаксис этой конструкции:
ВЫРАЗИТЬ(ТабРеквизиты.Значение КАК ЧИСЛО(15, 3))
Здесь мы явно указываем платформе, что значение дополнительного реквизита должно интерпретироваться как число заданной длины и точности. Параметр ЧИСЛО(15, 3) означает, что число может содержать до 15 знаков в целой части и до 3 знаков после запятой (что особенно важно для весовых или объемных показателей).
Важно: Если в базе данных по ошибке для какого-то элемента в это поле запишется значение другого типа (например, строка или ссылка), функция ВЫРАЗИТЬ вернет значение NULL, что предотвратит аварийное завершение работы запроса.
Так как дополнительные реквизиты связываются с основными таблицами (например, с таблицей оборотов регистра продаж или справочником номенклатуры) с помощью конструкции ЛЕВОЕ СОЕДИНЕНИЕ, у некоторых позиций номенклатуры этот реквизит может физически отсутствовать. В этом случае соединение вернет системное значение NULL.
Попытка просуммировать поля, содержащие NULL, может приводить к некорректным результатам или ошибкам расчетов. Чтобы этого избежать, необходимо всегда комбинировать приведение типов с функцией ЕСТЬNULL, заменяя пустые значения нулем:
ЕСТЬNULL(ВЫРАЗИТЬ(ТабРеквизиты.Значение КАК ЧИСЛО(15, 3)), 0) КАК УслЕд
Обратите внимание на последовательность вызовов: сначала мы выражаем значение как число, а затем оборачиваем всю конструкцию в ЕСТЬNULL. Это гарантирует, что на выходе мы всегда получим числовое значение (либо реальное число из реквизита, либо 0), готовое для математических операций и суммирования в секции ИТОГИ или СГРУППИРОВАТЬ ПО.
Если выполнять левое соединение со справочником дополнительных реквизитов и приведение типов прямо в основном теле сложного запроса (где уже есть виртуальные таблицы оборотов, группировки и итоги), производительность СУБД может значительно снизиться. Сложные подзапросы в соединениях мешают оптимизатору СУБД построить эффективный план выполнения.
Проанализируем ситуацию и применим лучшую практику разработки — вынесем получение дополнительных реквизитов во временную таблицу. Разберем этот процесс по шагам:
ВЫРАЗИТЬ.ЕСТЬNULL.Посмотрим на практическую реализацию этого подхода.
Ниже представлен пример оптимизированного пакета запросов, который решает проблему суммирования дополнительного реквизита «УслЕд» для номенклатуры в разрезе продаж.
// Шаг 1. Получаем числовые значения дополнительного реквизита во временную таблицу
ВЫБРАТЬ
НоменклатураДополнительныеРеквизиты.Ссылка КАК Ссылка,
ВЫРАЗИТЬ(НоменклатураДополнительныеРеквизиты.Значение КАК ЧИСЛО(15, 3)) КАК ЗначениеЧисло
ПОМЕСТИТЬ ВТ_ДопРеквизитыУслЕд
ИЗ
Справочник.Номенклатура.ДополнительныеРеквизиты КАК НоменклатураДополнительныеРеквизиты
ГДЕ
НоменклатураДополнительныеРеквизиты.Свойство.Имя = "УслЕд"
ИНДЕКСИРОВАТЬ ПО
Ссылка
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2. Основной запрос с левым соединением и расчетом итогов
ВЫБРАТЬ
ВыручкаОбороты.АналитикаУчетаНоменклатуры.Номенклатура КАК Номенклатура,
ВыручкаОбороты.АналитикаУчетаПоПартнерам.Партнер КАК Клиент,
ВыручкаОбороты.КоличествоОборот КАК Количество,
ВыручкаОбороты.СуммаВыручкиОборот КАК Сумма,
ЕСТЬNULL(ВТ_ДопРеквизитыУслЕд.ЗначениеЧисло, 0) КАК УслЕд
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&НачалоПериода, &КонецПериода, , ) КАК ВыручкаОбороты
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДопРеквизитыУслЕд КАК ВТ_ДопРеквизитыУслЕд
ПО ВыручкаОбороты.АналитикаУчетаНоменклатуры.Номенклатура = ВТ_ДопРеквизитыУслЕд.Ссылка
СГРУППИРОВАТЬ ПО
ВыручкаОбороты.АналитикаУчетаНоменклатуры.Номенклатура,
ВыручкаОбороты.АналитикаУчетаПоПартнерам.Партнер,
ВыручкаОбороты.КоличествоОборот,
ВыручкаОбороты.СуммаВыручкиОборот,
ЕСТЬNULL(ВТ_ДопРеквизитыУслЕд.ЗначениеЧисло, 0)
ИТОГИ
СУММА(Количество),
СУММА(Сумма),
СУММА(УслЕд)
ПО
ОБЩИЕ,
Номенклатура,
Клиент
ВЫРАЗИТЬ(... КАК ЧИСЛО) происходит изолированно на первом этапе, гарантируя, что в основную выборку попадут строго типизированные числовые данные.LEFT JOIN (ВЫБРАТЬ...) КАК Таб...), что упрощает его дальнейшую поддержку и модификацию другими разработчиками.Таким образом, комбинируя явное приведение типов ВЫРАЗИТЬ, обработку неопределенных значений ЕСТЬNULL и структурирование пакета запросов с помощью временных таблиц, мы получаем быстрое, надежное и отказоустойчивое решение для получения итогов по любым числовым дополнительным реквизитам.