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