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