Как использовать функции общих модулей в запросе СКД и получать цены на каждую дату?

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

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

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

Почему нельзя вызвать функцию общего модуля в запросе?

Разберем причину этого ограничения. Когда 1С выполняет запрос, его текст транслируется на язык SQL, понятный конкретной СУБД (MS SQL Server, PostgreSQL и др.). СУБД исполняет этот код на своей стороне. Она ничего не знает о метаданных 1С, встроенном языке или общих модулях. Попытка вставить вызов ОбщиеМодули.Ценообразование.ПолучитьЦену() в блок ВЫБРАТЬ приведет к синтаксической ошибке, так как транслятор не сможет преобразовать этот вызов в SQL-команду.

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

Решение 1: Формирование «каркаса» данных через временные таблицы

Если нам необходимо получить остатки или цены на каждый день периода в рамках одного запроса, мы должны отказаться от идеи вызова функции и реализовать логику «на чистом SQL». Рассмотрим алгоритм создания таблицы, где для каждой номенклатуры будет указана цена на каждый день месяца.

Шаг 1. Создание таблицы всех дат периода.

Для этого можно использовать производственный календарь или сформировать таблицу программно. В запросе это выглядит как соединение таблицы календаря с вашим списком номенклатуры.


ВЫБРАТЬ
    РегламентированныйПроизводственныйКалендарь.ДатаКалендаря КАК Период
ПОМЕСТИТЬ вт_Календарь
ИЗ
    РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК РегламентированныйПроизводственныйКалендарь
ГДЕ
    РегламентированныйПроизводственныйКалендарь.ДатаКалендаря МЕЖДУ &НачалоПериода И &КонецПериода;

Шаг 2. Создание декартова произведения «Дата - Номенклатура».

Проанализируем ситуацию: чтобы получить цену на каждый день, нам сначала нужна сетка, где для каждой позиции номенклатуры будет создана строка на каждую дату из вт_Календарь.


ВЫБРАТЬ
    вт_Календарь.Период,
    Номенклатура.Ссылка КАК Номенклатура
ПОМЕСТИТЬ вт_Сетка
ИЗ
    вт_Календарь КАК вт_Календарь,
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.ЭтоГруппа = ЛОЖЬ;

Шаг 3. Соединение с регистром цен.

Теперь мы должны «подтянуть» актуальную цену. Важно понимать, что цена в регистре сведений хранится не на каждый день, а только в момент изменения. Поэтому мы используем соединение по условию Сетка.Период >= Цены.Период.


ВЫБРАТЬ
    вт_Сетка.Период,
    вт_Сетка.Номенклатура,
    МАКСИМУМ(Цены.Период) КАК ПериодЦены
ПОМЕСТИТЬ вт_АктуальныеДатыЦен
ИЗ
    вт_Сетка КАК вт_Сетка
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
        ПО вт_Сетка.Номенклатура = Цены.Номенклатура
        И вт_Сетка.Период >= Цены.Период
ГРУППИРОВАТЬ ПО
    вт_Сетка.Период,
    вт_Сетка.Номенклатура;

Выясним причину использования функции МАКСИМУМ: для каждой даты из сетки мы ищем самую позднюю дату записи в регистре, которая меньше или равна текущей дате календаря. После этого остается соединить вт_АктуальныеДатыЦен с самим регистром для получения числового значения цены.

Решение 2: Использование «Порождающих запросов» в СКД

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

  1. Создадим НаборДанных1 (запрос), который выбирает список номенклатуры и нужные даты.
  2. Создадим НаборДанных2 (запрос), который получает цену на одну конкретную дату. В тексте запроса используем параметры: ГДЕ Период = &ПараметрДата И Номенклатура = &ПараметрНоменклатура.
  3. На закладке «Связи наборов данных» укажем:
    • Источник связи: НаборДанных1.
    • Приемник связи: НаборДанных2.
    • Условие связи: Номенклатура = ПараметрНоменклатура, Дата = ПараметрДата.

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

Решение 3: Внешние наборы данных (Таблица значений)

Если логика в общем модуле слишком сложна (например, ПолучитьЦенуНоменклатуры в УТ 10.3 учитывает иерархию типов цен, курсы валют, характеристики и спец. условия), то проще всего рассчитать эти данные кодом 1С перед запуском отчета.

Проанализируем шаги реализации:

  1. В модуле отчета перед компоновкой результата выполняем запрос для получения списка нужной номенклатуры и дат.
  2. Выгружаем результат в ТаблицуЗначений.
  3. Добавляем в таблицу колонку Цена.
  4. В цикле обходим таблицу и для каждой строки вызываем ту самую «замечательную» функцию: Строка.Цена = Ценообразование.ПолучитьЦенуНоменклатуры(Строка.Номенклатура, Строка.Дата, ...).
  5. Передаем готовую таблицу в СКД как внешний набор данных через ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных).

Важный момент: использование внешних наборов данных позволяет задействовать любую сложность бизнес-логики 1С, которую невозможно воспроизвести в чистом SQL-запросе.

Проблема остатков без оборотов (Виртуальные таблицы)

В ходе дискуссии была затронута важная тема: виртуальная таблица ОстаткиИОбороты часто «теряет» дни, в которых не было движений, если к ней обращаться без правильных настроек дополнения периодов. Если же нам нужно использовать остатки во временных таблицах внутри запроса, ОстаткиИОбороты не поможет.

Разберем правильный подход: используйте виртуальную таблицу Остатки, передавая в неё параметр Период из нашей временной таблицы дат (сетки). Как показано в Решении 1, соединение сетки дат с регистром остатков по принципу «Максимальная дата движения <= Дата календаря» гарантирует получение актуального остатка на любой день, независимо от наличия оборотов.

Оптимизация: Индексы во временных таблицах

При работе с большими объемами данных в пакете запросов (например, 1000 товаров * 30 дней = 30 000 строк) критически важно использовать индексацию. Рассмотрим пример правильной подготовки временной таблицы:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    вт_Сетка.Номенклатура
ПОМЕСТИТЬ вт_ИндексированнаяНоменклатура
ИЗ
    вт_Сетка КАК вт_Сетка
ИНДЕКСИРОВАТЬ ПО
    Номенклатура;

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

← На главную