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