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