Как просуммировать значение дополнительного реквизита в запросе 1С?

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

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

В чем причина проблемы? Выясняем суть

Для начала проанализируем структуру хранения данных дополнительных реквизитов. В типовых конфигурациях (таких как ERP, Комплексная автоматизация, Управление торговлей или УНФ) дополнительные реквизиты хранятся в табличной части объекта (например, Справочник.Номенклатура.ДополнительныеРеквизиты). Эта табличная часть содержит реквизиты Свойство (ссылка на план видов характеристик) и Значение.

Поле Значение на уровне метаданных имеет составной тип данных. Оно может содержать:

Даже если для конкретного свойства (например, «Условные единицы» или «Объем в литрах») в пользовательском режиме вы жестко задали тип «Число», для СУБД колонка Значение все равно остается составного типа. СУБД (будь то Microsoft SQL Server, PostgreSQL или любая другая) физически не может применить агрегатную функцию СУММА к полю составного типа, так как теоретически там могут находиться строки или ссылки. Это ограничение платформы 1С и реляционных баз данных приводит к ошибкам при компиляции или выполнении запроса.

Почему СКД суммирует без проблем, а запрос — нет?

Многие разработчики замечают, что при выводе тех же данных через Систему компоновки данных (СКД) суммирование полей дополнительных реквизитов в ресурсах работает корректно и без дополнительных манипуляций. Почему так происходит?

Дело в том, что СКД — это интеллектуальный генератор запросов. Когда вы указываете поле в качестве ресурса с функцией СУММА, СКД анализирует схему компоновки данных, определяет реальный тип значения и на уровне генерации итогового SQL-запроса автоматически подставляет конструкции явного приведения типов (аналог SQL-функции CAST). Обычный же запрос (создаваемый через объект Новый Запрос в коде) транслируется в SQL практически «как есть», перекладывая всю ответственность за типизацию на разработчика.

Рассмотрим, как решить эту проблему в обычном запросе по шагам.

Решение 1: Явное приведение типов с помощью функции ВЫРАЗИТЬ

Самый надежный и правильный способ заставить СУБД просуммировать значение дополнительного реквизита — это жестко ограничить его тип в самом запросе до числового. Для этого используется встроенная функция языка запросов 1С — ВЫРАЗИТЬ().

Рассмотрим синтаксис этой конструкции:

ВЫРАЗИТЬ(ТабРеквизиты.Значение КАК ЧИСЛО(15, 3))

Здесь мы явно указываем платформе, что значение дополнительного реквизита должно интерпретироваться как число заданной длины и точности. Параметр ЧИСЛО(15, 3) означает, что число может содержать до 15 знаков в целой части и до 3 знаков после запятой (что особенно важно для весовых или объемных показателей).

Важно: Если в базе данных по ошибке для какого-то элемента в это поле запишется значение другого типа (например, строка или ссылка), функция ВЫРАЗИТЬ вернет значение NULL, что предотвратит аварийное завершение работы запроса.

Решение 2: Обработка пустых значений с помощью функции ЕСТЬNULL

Так как дополнительные реквизиты связываются с основными таблицами (например, с таблицей оборотов регистра продаж или справочником номенклатуры) с помощью конструкции ЛЕВОЕ СОЕДИНЕНИЕ, у некоторых позиций номенклатуры этот реквизит может физически отсутствовать. В этом случае соединение вернет системное значение NULL.

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

ЕСТЬNULL(ВЫРАЗИТЬ(ТабРеквизиты.Значение КАК ЧИСЛО(15, 3)), 0) КАК УслЕд

Обратите внимание на последовательность вызовов: сначала мы выражаем значение как число, а затем оборачиваем всю конструкцию в ЕСТЬNULL. Это гарантирует, что на выходе мы всегда получим числовое значение (либо реальное число из реквизита, либо 0), готовое для математических операций и суммирования в секции ИТОГИ или СГРУППИРОВАТЬ ПО.

Решение 3: Оптимизация запроса с использованием временных таблиц (Лучшая практика)

Если выполнять левое соединение со справочником дополнительных реквизитов и приведение типов прямо в основном теле сложного запроса (где уже есть виртуальные таблицы оборотов, группировки и итоги), производительность СУБД может значительно снизиться. Сложные подзапросы в соединениях мешают оптимизатору СУБД построить эффективный план выполнения.

Проанализируем ситуацию и применим лучшую практику разработки — вынесем получение дополнительных реквизитов во временную таблицу. Разберем этот процесс по шагам:

  1. Создаем временную таблицу, в которую выбираем только нужные нам свойства (например, «УслЕд») для конкретных объектов номенклатуры.
  2. В этой же временной таблице сразу выполняем жесткое приведение типов с помощью ВЫРАЗИТЬ.
  3. Индексируем временную таблицу по ключевому полю связи (по ссылке на номенклатуру).
  4. В основном запросе выполняем простое и быстрое левое соединение с подготовленной временной таблицей, используя ЕСТЬNULL.

Посмотрим на практическую реализацию этого подхода.

Ниже представлен пример оптимизированного пакета запросов, который решает проблему суммирования дополнительного реквизита «УслЕд» для номенклатуры в разрезе продаж.


// Шаг 1. Получаем числовые значения дополнительного реквизита во временную таблицу
ВЫБРАТЬ
    НоменклатураДополнительныеРеквизиты.Ссылка КАК Ссылка,
    ВЫРАЗИТЬ(НоменклатураДополнительныеРеквизиты.Значение КАК ЧИСЛО(15, 3)) КАК ЗначениеЧисло
ПОМЕСТИТЬ ВТ_ДопРеквизитыУслЕд
ИЗ
    Справочник.Номенклатура.ДополнительныеРеквизиты КАК НоменклатураДополнительныеРеквизиты
ГДЕ
    НоменклатураДополнительныеРеквизиты.Свойство.Имя = "УслЕд"

ИНДЕКСИРОВАТЬ ПО
    Ссылка
;

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

СГРУППИРОВАТЬ ПО
    ВыручкаОбороты.АналитикаУчетаНоменклатуры.Номенклатура,
    ВыручкаОбороты.АналитикаУчетаПоПартнерам.Партнер,
    ВыручкаОбороты.КоличествоОборот,
    ВыручкаОбороты.СуммаВыручкиОборот,
    ЕСТЬNULL(ВТ_ДопРеквизитыУслЕд.ЗначениеЧисло, 0)

ИТОГИ
    СУММА(Количество),
    СУММА(Сумма),
    СУММА(УслЕд)
ПО
    ОБЩИЕ,
    Номенклатура,
    Клиент

Преимущества использования временных таблиц в данном кейсе:

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

← На главную