При построении аналитических отчетов и проведении расчетов часто возникает задача: получить актуальные значения ресурсов периодического регистра сведений на каждый день заданного интервала дат (аналог вызова виртуальной таблицы СрезПоследних для каждого дня по отдельности). Типичная проблема, с которой сталкиваются разработчики — пропадание из выборки дней в начале периода, если первая запись в регистре появилась позже даты старта отчета.
Выясним причину такого поведения запроса и подробно разберем правильные варианты реализации данной задачи в платформе 1С:Предприятие.
Рассмотрим, почему первоначальный вариант запроса выдавал данные только с 23 числа при периоде в 31 день:
ВНУТРЕННЕЕ СОЕДИНЕНИЕ таблицы календарных дней с таблицей регистра сведений по нестрогому условию СубсидииКонтрагентов.Период <= ПериодПоДням.ДатаПериода. Если до 23 числа в регистре не было ни одной записи по контрагенту, условие не выполнялось ни для одного дня с 1 по 22 число, и эти даты просто отсекались из результирующей выборки.СрезПоследних нельзя динамически передать поле из другой таблицы в качестве параметра Период. Поэтому срез на каждую дату приходится формировать вручную с помощью временных таблиц или интервалов.ГДЕ: Отбор в условии через вложенную выборку первых записей также исключает строки, для которых в подзапросе не найдено подходящей даты.Разберем классический подход к получению среза на каждый день. Алгоритм состоит из трех основных этапов:
Период для каждой пары «Контрагент – ДатаПериода» и через соединение выбираем итоговые ресурсы.Посмотрим на практический пример реализации запроса:
ВЫБРАТЬ
КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, РазностьДат.НомерДняИзДиапазона), ДЕНЬ) КАК ДатаПериода
ПОМЕСТИТЬ ПериодПоДням
ИЗ
(ВЫБРАТЬ
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 или пустыми субсидиями), в финальном пакете базовой таблицей должна выступать исходная таблица дней, объединенная через ЛЕВОЕ СОЕДИНЕНИЕ с выборкой срезов.
Проанализируем производительность предыдущего метода. Соединение ПО Период <= ДатаПериода генерирует квадратичное количество строк, что на больших объемах регистра и широких диапазонах дат приведет к замедлению запроса.
Более производительный путь — интервальный метод. Разберем его по шагам:
ДатаНачала (ее исходный период) и ДатаОкончания (период следующей по времени записи по этому же контрагенту).ДатаПериода >= ДатаНачала И (ДатаПериода < ДатаОкончания ИЛИ ДатаОкончания ЕСТЬ NULL).Такой подход полностью исключает избыточные декартовы произведения строк и существенно снижает нагрузку на СУБД.
Если задача решается в рамках Системы компоновки данных (СКД), строить сложные запросы со срезами на каждый день не требуется. Рассмотрим два встроенных механизма:
РегистрСведений.СубсидииКонтрагентов.СрезПоследних(&ПериодСреза, ). На вкладке «Связи наборов данных» связываем их так, чтобы поле ДатаПериода родительского набора передавалось в параметр &ПериодСреза дочернего набора. Платформа рассчитает срез на каждый день автоматически.Период можно включить флаг «Дополнять периоды» с типом дополнения «День». Платформа автоматически заполнит пропущенные даты в отчете.