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