Как использовать реквизиты параметра-документа в запросах и СКД 1С?

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

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

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

1. Почему нельзя просто написать &Документ.Дата в параметрах виртуальной таблицы?

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


ВЫБРАТЬ
    ДвиженияОстатки.Номенклатура КАК Номенклатура,
    ДвиженияОстатки.КоличествоОстаток КАК КоличествоОстаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&Документ.Дата, ) КАК ДвиженияОстатки

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

Вторая распространенная попытка обхода этого ограничения выглядит так:


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

Эта конструкция также не сработает непосредственно в круглых скобках параметров виртуальной таблицы. Платформа «1С» не поддерживает использование конструкции ВЫРАЗИТЬ внутри параметров виртуальных таблиц непосредственно в тексте запроса. Попытка обернуть выражение в скобки приведет к ошибке разбора запроса.

2. Проблемы производительности при разыменовании параметров

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

Когда мы пишем в запросе конструкцию вида &Документ.Дата, платформа выполняет операцию разыменования. На уровне СУБД это преобразуется в неявное левое соединение (LEFT OUTER JOIN) основной таблицы с таблицей соответствующего документа для получения значения его реквизита «Дата».

Ситуация значительно усугубляется, если параметр &Документ имеет составной тип данных (например, когда в качестве документа может выступать и «Реализация товаров услуг», и «Отчет о розничных продажах», и «Перемещение товаров»). В этом случае платформа «1С» вынуждена сгенерировать SQL-запрос, который выполнит левое соединение со всеми физическими таблицами документов, входящих в данный составной тип. На уровне СУБД это будет выглядеть как громоздкая цепочка соединений, что приведет к созданию неоптимального плана запроса, избыточной нагрузке на процессор сервера БД и драматическому росту времени выполнения запроса на больших объемах данных.

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

Золотое правило разработки на 1С: Всегда передавайте в запрос уже готовую дату (примитивный тип Дата) или готовый объект Граница/МоментВремени в качестве отдельного параметра. Изизбегайте разыменования параметров внутри запроса.

3. Методологическая ошибка: Выбор «Даты» вместо «Момента времени» или «Границы»

Помимо синтаксических ограничений и проблем производительности, использование чистой даты документа в качестве периода получения остатков часто является логической ошибкой.

Выясним причину этой проблемы. На оси времени «1С» дата представляет собой точку с точностью до секунды. На одну и ту же секунду в системе может быть записано и проведено несколько документов. Если мы передаем в виртуальную таблицу остатков просто дату со временем (например, 31.12.2023 12:00:00), то система вернет остатки на начало этой секунды. Это означает, что:

Для корректного получения остатков в платформе «1С» предусмотрены специальные типы данных: МоментВремени и Граница.

Рассмотрим подробнее каждый из них:

  1. Момент времени (МоментВремени) — это объект, объединяющий в себе дату-время документа и уникальную ссылку на сам документ. Он гарантирует однозначное позиционирование документа на оси времени, даже если в одну секунду записано множество документов. Передача момента времени в виртуальную таблицу остатков вернет остатки на момент "до" проведения этого документа. То есть движения самого документа включены в остаток не будут.
  2. Граница (Граница) — объект, позволяющий явно указать, включать ли движения граничного момента в результат запроса или нет. Для этого используется перечисление ВидГраницы.

Проанализируем примеры использования этих объектов в коде.

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
    |   ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток
    |ИЗ
    |   РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК ТоварыНаСкладахОстатки";

// Передаем момент времени документа. Движения документа НЕ учитываются.
Запрос.УстановитьПараметр("Период", СсылкаНаДокумент.МоментВремени());
РезультатЗапроса = Запрос.Выполнить();

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ВзаиморасчетыОстатки.Клиент КАК Клиент,
    |   ВзаиморасчетыОстатки.СуммаОстаток КАК СуммаОстаток
    |ИЗ
    |   РегистрНакопления.Взаиморасчеты.Остатки(&Период, ) КАК ВзаиморасчетыОстатки";

// Формируем границу, включающую момент времени документа. Движения документа учитываются.
ГраницаПериода = Новый Граница(СсылкаНаДокумент.МоментВремени(), ВидГраницы.Включая);

Запрос.УстановитьПараметр("Период", ГраницаПериода);
РезультатЗапроса = Запрос.Выполнить();

4. Идеальное решение в СКД: Использование выражений параметров

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

Для решения этой задачи в СКД предусмотрен элегантный встроенный механизм — выражения параметров. Мы можем настроить схему компоновки таким образом, что для пользователя будет доступен только параметр «Документ», а системный параметр «Период» будет рассчитываться автоматически на уровне платформы.

Разберем по шагам, как это реализовать:

  1. Создаем запрос в СКД: В тексте запроса виртуальной таблицы используем стандартный параметр &Период.
    
    ВЫБРАТЬ
        ПродажиОстатки.Товар КАК Товар,
        ПродажиОстатки.КоличествоОстаток КАК КоличествоОстаток
    ИЗ
        РегистрНакопления.Продажи.Остатки(&Период, ) КАК ПродажиОстатки
    
  2. Добавляем параметр «Документ»: Переходим на вкладку Параметры в конструкторе схемы компоновки данных. Если платформа автоматически не добавила параметр &Документ, добавляем его вручную, нажав кнопку «Добавить». Указываем для него имя «Документ» и выбираем соответствующий тип (ссылку на документ).
  3. Настраиваем выражение для параметра «Период»: Находим в списке параметров строку Период. В колонке Выражение пишем специальное выражение на языке СКД: &Документ.МоментВремени (или &Документ.Дата, если методологически требуется именно дата). СКД поддерживает такое обращение на уровне своего собственного процессора компоновки.
  4. Скрываем параметр «Период» от пользователя: Для параметра Период устанавливаем флаг Ограничение доступности. Это исключит его из списка параметров, доступных пользователю при настройке отчета на форме. Пользователь будет видеть только красивое поле выбора документа.
  5. Делаем параметр «Документ» обязательным: Для параметра Документ снимаем флаг «Ограничение доступности» и, при необходимости, устанавливаем флаг «Запрещать пустые значения».

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

5. Альтернативный вариант настройки СКД с использованием Границы

Что делать, если в СКД нам нужно получить остатки именно включая движения выбранного документа? В выражениях СКД можно использовать конструкцию создания объекта Граница. Посмотрим на пример выражения для параметра Период в этом случае:

В поле Выражение для параметра Период пишем конструкцию:

Новый Граница(&Документ.МоментВремени, "Включая")

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

Таким образом, мы решили проблему: избавились от дублирования параметров, сохранили высокую производительность СУБД за счет исключения неявных левых соединений в теле запроса и обеспечили методологически правильный расчет остатков на оси времени 1С.

← На главную