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