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

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

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

Выясним причину такого поведения запроса и подробно разберем правильные варианты реализации данной задачи в платформе 1С:Предприятие.

Анализ причин проблемы в исходном запросе

Рассмотрим, почему первоначальный вариант запроса выдавал данные только с 23 числа при периоде в 31 день:

  1. Тип соединения таблиц: В запросе использовалось ВНУТРЕННЕЕ СОЕДИНЕНИЕ таблицы календарных дней с таблицей регистра сведений по нестрогому условию СубсидииКонтрагентов.Период <= ПериодПоДням.ДатаПериода. Если до 23 числа в регистре не было ни одной записи по контрагенту, условие не выполнялось ни для одного дня с 1 по 22 число, и эти даты просто отсекались из результирующей выборки.
  2. Ограничение виртуальных таблиц: В виртуальную таблицу СрезПоследних нельзя динамически передать поле из другой таблицы в качестве параметра Период. Поэтому срез на каждую дату приходится формировать вручную с помощью временных таблиц или интервалов.
  3. Коррелированный подзапрос в секции ГДЕ: Отбор в условии через вложенную выборку первых записей также исключает строки, для которых в подзапросе не найдено подходящей даты.

Решение 1: Формирование среза через группировку и временную таблицу

Разберем классический подход к получению среза на каждый день. Алгоритм состоит из трех основных этапов:

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

Посмотрим на практический пример реализации запроса:


ВЫБРАТЬ
    КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, РазностьДат.НомерДняИзДиапазона), ДЕНЬ) КАК ДатаПериода
ПОМЕСТИТЬ ПериодПоДням
ИЗ
    (ВЫБРАТЬ
        aa.a * 1000 + bb.b * 100 + cc.c * 10 + dd.d КАК НомерДняИзДиапазона
    ИЗ
        (ВЫБРАТЬ 0 КАК a ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК aa
        ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК b ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК bb ПО (ИСТИНА)
        ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК c ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК cc ПО (ИСТИНА)
        ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК d ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК dd ПО (ИСТИНА)
    ГДЕ
        aa.a * 1000 + bb.b * 100 + cc.c * 10 + dd.d <= РАЗНОСТЬДАТ(&НачалоПериода, &КонецПериода, ДЕНЬ)) КАК РазностьДат
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    СубсидииКонтрагентов.Контрагент КАК Контрагент,
    СубсидииКонтрагентов.Субсидия КАК Субсидия,
    СубсидииКонтрагентов.Период КАК Период,
    ПериодПоДням.ДатаПериода КАК ДатаПериода
ПОМЕСТИТЬ Табличка
ИЗ
    РегистрСведений.СубсидииКонтрагентов КАК СубсидииКонтрагентов
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПериодПоДням КАК ПериодПоДням
        ПО СубсидииКонтрагентов.Период <= ПериодПоДням.ДатаПериода
ИНДЕКСИРОВАТЬ ПО
    Контрагент,
    Период,
    ДатаПериода
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Табличка.Контрагент КАК Контрагент,
    Табличка.ДатаПериода КАК ДатаПериода,
    Табличка.Субсидия КАК Субсидия
ИЗ
    Табличка КАК Табличка
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
            МАКСИМУМ(Табличка.Период) КАК Период,
            Табличка.Контрагент КАК Контрагент,
            Табличка.ДатаПериода КАК ДатаПериода
        ИЗ
            Табличка КАК Табличка
        СГРУППИРОВАТЬ ПО
            Табличка.ДатаПериода,
            Табличка.Контрагент) КАК Фильтр
        ПО (Фильтр.Период = Табличка.Период)
            И (Фильтр.ДатаПериода = Табличка.ДатаПериода)
            И (Фильтр.Контрагент = Табличка.Контрагент)
УПОРЯДОЧИТЬ ПО
    ДатаПериода,
    Контрагент

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

Решение 2: Оптимизация через интервалы действия записей

Проанализируем производительность предыдущего метода. Соединение ПО Период <= ДатаПериода генерирует квадратичное количество строк, что на больших объемах регистра и широких диапазонах дат приведет к замедлению запроса.

Более производительный путь — интервальный метод. Разберем его по шагам:

  1. Для каждой записи регистра рассчитываем ДатаНачала (ее исходный период) и ДатаОкончания (период следующей по времени записи по этому же контрагенту).
  2. Соединяем таблицу дней с интервалами по строгому условию: ДатаПериода >= ДатаНачала И (ДатаПериода < ДатаОкончания ИЛИ ДатаОкончания ЕСТЬ NULL).

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

Альтернатива: Использование возможностей СКД

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

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