При разработке сложных отчетов в системе компоновки данных (СКД) часто возникает задача, когда границы периода не задаются пользователем напрямую, а должны быть вычислены программно внутри самого запроса. Типичный пример: необходимо выбрать документы оприходования, которые были созданы между двумя моментами времени — например, между последним пересчетом товаров за вчерашний день и последним пересчетом за сегодня. В данной статье мы подробно разберем, как реализовать такую логику, используя временные таблицы и эффективные соединения.
Представим ситуацию: у нас есть базовая дата (параметр &Дата). Нам нужно найти в базе данных два конкретных документа ПересчетТоваров:
Даты этих двух документов станут границами «Начала» и «Конца» для выборки строк из документов ОприходованиеИзлишковТоваров. Основная сложность заключается в том, чтобы сделать это в рамках одного запроса (одного набора данных СКД), не прибегая к предварительным расчетам во встроенном языке.
Для начала нам необходимо вычислить начальную и конечную точки отсчета. Мы воспользуемся конструкцией ВЫБРАТЬ ПЕРВЫЕ 1 в сочетании с сортировкой по убыванию даты. Проанализируем, как получить дату последнего пересчета за предыдущий день:
ВЫБРАТЬ ПЕРВЫЕ 1
ПересчетТоваров.Дата КАК ДатаНачалаОтсчёта
ПОМЕСТИТЬ ВтПересчетНачалоПрихода
ИЗ
Документ.ПересчетТоваров КАК ПересчетТоваров
ГДЕ
НАЧАЛОПЕРИОДА(ПересчетТоваров.Дата, ДЕНЬ) = ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ), ДЕНЬ, -1)
И ПересчетТоваров.Проведен = ИСТИНА
УПОРЯДОЧИТЬ ПО
ПересчетТоваров.Дата УБЫВ;
Аналогичным образом вычислим дату последнего пересчета за текущий день:
ВЫБРАТЬ ПЕРВЫЕ 1
ПересчетТоваров.Дата КАК ДатаОкончанияОтсчёта
ПОМЕСТИТЬ ВтПересчетКонецПрихода
ИЗ
Документ.ПересчетТоваров КАК ПересчетТоваров
ГДЕ
НАЧАЛОПЕРИОДА(ПересчетТоваров.Дата, ДЕНЬ) = НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ)
И ПересчетТоваров.Проведен = ИСТИНА
УПОРЯДОЧИТЬ ПО
ДатаОкончанияОтсчёта УБЫВ;
Важный момент: обратите внимание на условие ПересчетТоваров.Проведен = ИСТИНА. Проверка статуса проведения критически важна, чтобы случайные черновики документов не исказили границы вашего периода.
Теперь объединим полученные значения в одну временную таблицу ВтПериод. Поскольку в каждой из исходных таблиц у нас ровно по одной строке (благодаря ПЕРВЫЕ 1), мы можем соединить их по условию ИСТИНА (декартово произведение), что даст нам одну строку с двумя колонками.
ВЫБРАТЬ
ВтПересчетКонецПрихода.ДатаОкончанияОтсчёта КАК ДатаОкончанияОтсчёта,
ВтПересчетНачалоПрихода.ДатаНачалаОтсчёта КАК ДатаНачалаОтсчёта
ПОМЕСТИТЬ ВтПериод
ИЗ
ВтПересчетНачалоПрихода КАК ВтПересчетНачалоПрихода
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВтПересчетКонецПрихода КАК ВтПересчетКонецПрихода ПО (ИСТИНА);
На заключительном этапе мы выбираем данные из табличной части целевого документа. Чтобы использовать вычисленные ранее даты, мы выполним ВНУТРЕННЕЕ СОЕДИНЕНИЕ с нашей таблицей ВтПериод. Условием соединения будет вхождение даты документа в интервал между началом и концом.
ВЫБРАТЬ
ОприходованиеТовары.Ссылка КАК Ссылка,
ОприходованиеТовары.Номенклатура КАК Номенклатура,
ОприходованиеТовары.Количество КАК Количество,
ОприходованиеТовары.Цена КАК Цена,
ОприходованиеТовары.Сумма КАК Сумма
ИЗ
ВтПериод КАК ВтПериод
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ОприходованиеИзлишковТоваров.Товары КАК ОприходованиеТовары
ПО ОприходованиеТовары.Ссылка.Дата МЕЖДУ ВтПериод.ДатаНачалаОтсчёта И ВтПериод.ДатаОкончанияОтсчёта
ГДЕ
ОприходованиеТовары.Ссылка.Проведен = ИСТИНА;
В этом решении мы используем конструкцию ПО ... МЕЖДУ непосредственно в условии соединения. Это позволяет эффективно отсечь лишние записи еще на этапе сопоставления таблиц.
Рассмотрим несколько нюансов, которые помогут сделать ваш запрос быстрее и надежнее:
Документ.ОприходованиеИзлишковТоваров.Товары — не самый оптимальный путь. Если база данных большая, лучше выбирать данные из соответствующих регистров накопления (например, ТоварыНаСкладах). Это обеспечит автоматическую фильтрацию только по проведенным документам и избавит от лишних соединений с заголовками документов.ОприходованиеТовары.Ссылка.Дата заставляет СУБД выполнять неявное соединение табличной части с основной таблицей документа. Для повышения производительности рекомендуется сначала отобрать ссылки на документы во временную таблицу, отфильтровав их по дате в основной физической таблице Документ.ОприходованиеИзлишковТоваров, а затем соединять их с товарами.ПересчетТоваров, таблица ВтПересчетНачалоПрихода будет пустой. В результате ВНУТРЕННЕЕ СОЕДИНЕНИЕ уничтожит все данные, и отчет ничего не выведет. Если такая ситуация возможна, рассмотрите вариант использования ЛЕВОЕ СОЕДИНЕНИЕ и функции ЕСТЬNULL для установки границ по умолчанию.Если вы работаете исключительно в СКД, можно пойти иным путем. Вместо сложных конструкций внутри запроса, мы можем определить параметры ДатаНачала и ДатаОкончания на закладке «Параметры» в конструкторе схемы компоновки данных. В поле «Выражение» для этих параметров можно написать вызов функции общего модуля, которая вернет нужные даты, или использовать язык выражений СКД.
Например, в основном запросе вы просто пишете:
ГДЕ Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
А логику поиска последних документов пересчета выносите в отдельную функцию. Это сделает основной запрос гораздо более читаемым и легким в поддержке.
Проанализируем технические требования к оформлению кода. При написании запросов и переменных в 1С крайне рекомендуется придерживаться системы стандартов и методик разработки. В частности, стоит избегать использования буквы «ё» в именах полей запроса и переменных (например, вместо ДатаНачалаОтсчёта лучше написать ДатаНачалаОтсчета). Это не является синтаксической ошибкой, но считается хорошим тоном и исключает проблемы при взаимодействии с некоторыми внешними СУБД или при парсинге кода сторонними инструментами.
Таким образом, мы рассмотрели полноценный механизм вычисления периодов «на лету». Использование временных таблиц для хранения границ интервалов — это мощный инструмент, который позволяет реализовывать сложную бизнес-логику в отчетах без написания лишнего кода на языке 1С.