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

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

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

В этой статье мы подробно разберем, как обойти это ограничение. Мы рассмотрим четыре проверенных практикой способа (workaround'а), которые позволяют решать эту задачу в коде, отчетах и при работе с регистрами сведений.

Способ 1. Использование связей наборов данных в СКД (Рекомендуемый для отчетов)

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

Разберем этот метод по шагам:

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

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

Способ 2. Получение остатков через физическую таблицу движений (Для кода)

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

Давайте проанализируем схему такого запроса. Предположим, что у нас уже есть временная таблица ВТ_ИсходныеДанные с полями Товар и МояДата:


ВЫБРАТЬ
    Исходные.Товар КАК Товар,
    Исходные.МояДата КАК МояДата,
    ЕСТЬNULL(СУММА(ВЫБОР
        КОГДА Движения.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
            ТОГДА Движения.Количество
        ИНАЧЕ -Движения.Количество
    КОНЕЦ), 0) КАК Остаток
ИЗ
    ВТ_ИсходныеДанные КАК Исходные
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах КАК Движения
        ПО Исходные.Товар = Движения.Номенклатура
        И Движения.Период <= Исходные.МояДата
СГРУППИРОВАТЬ ПО
    Исходные.Товар,
    Исходные.МояДата

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

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

Способ 3. Виртуальная таблица «ОстаткиИОбороты» с периодичностью (Оптимальный для кода)

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

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

Разберем алгоритм по шагам в рамках одного пакетного запроса:


// Шаг 1. Готовим исходные данные во временной таблице
ВЫБРАТЬ
    Товары.Ссылка КАК Товар,
    Товары.МояДата КАК МояДата
ПОМЕСТИТЬ ВТ_ИсходныеДанные
ИЗ
    Справочник.Товары КАК Товары;

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

////////////////////////////////////////////////////////////////////////////////
// Шаг 3. Сопоставляем исходную дату с ближайшим предшествующим периодом остатков
ВЫБРАТЬ
    Исходные.Товар КАК Товар,
    Исходные.МояДата КАК МояДата,
    МАКСИМУМ(ОстаткиПоДням.Период) КАК БлижайшийПериод
ПОМЕСТИТЬ ВТ_СопоставленныеДаты
ИЗ
    ВТ_ИсходныеДанные КАК Исходные
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ОстаткиПоДням КАК ОстаткиПоДням
        ПО Исходные.Товар = ОстаткиПоДням.Товар
        И ОстаткиПоДням.Период <= Исходные.МояДата
СГРУППИРОВАТЬ ПО
    Исходные.Товар,
    Исходные.МояДата;

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

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

Способ 4. Получение среза последних для Регистров Сведений

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

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


// Шаг 1. Собираем уникальные пары Товар-ДатаДокумента во временную таблицу
ВЫБРАТЬ РАЗЛИЧНЫЕ
    ДокТЧ.Номенклатура КАК Номенклатура,
    ДокТЧ.Ссылка.Дата КАК ДатаДокумента
ПОМЕСТИТЬ ВТ_ИсходныеЦены
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК ДокТЧ;

////////////////////////////////////////////////////////////////////////////////
// Шаг 2. Находим максимальную дату записи в регистре цен, которая меньше или равна дате документа
ВЫБРАТЬ
    Исходные.Номенклатура КАК Номенклатура,
    Исходные.ДатаДокумента КАК ДатаДокумента,
    МАКСИМУМ(ЦеныРегистр.Период) КАК МаксПериод
ПОМЕСТИТЬ ВТ_МаксПериодыЦен
ИЗ
    ВТ_ИсходныеЦены КАК Исходные
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныРегистр
        ПО Исходные.Номенклатура = ЦеныРегистр.Номенклатура
        И ЦеныРегистр.Период <= Исходные.ДатаДокумента
СГРУППИРОВАТЬ ПО
    Исходные.Номенклатура,
    Исходные.ДатаДокумента;

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

Этот трехшаговый алгоритм является стандартным шаблоном (best practice) в разработке на платформе 1С для эмуляции среза последних на различные даты внутри одного запроса. Он демонстрирует высокую производительность благодаря эффективному использованию индексов по измерениям и периоду регистра сведений.

← На главную