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