В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача получения нескольких последних значений для каждой группы данных. Типичный пример — вывод трех последних цен закупки для списка товаров. Реализация такой задачи в цикле (отдельный запрос на каждый товар) является крайне неоптимальной и может привести к серьезным задержкам при работе с большими массивами данных. Разберем наиболее эффективные способы решения этой задачи с помощью механизмов запросов.
Этот метод часто встречается в современных типовых конфигурациях, таких как 1С:Управление торговлей 11 или ERP. Его суть заключается в каскадном создании временных таблиц, в каждой из которых мы находим очередную максимальную дату, исключая уже найденные ранее даты. Проанализируем этот алгоритм по шагам.
Шаг 1: Находим дату самой последней закупки. Для этого мы группируем данные по номенклатуре (и характеристике, если нужно) и выбираем максимум по полю Дата.
ВЫБРАТЬ
Т.Номенклатура КАК Номенклатура,
МАКСИМУМ(Товары.Ссылка.Дата) КАК ДатаПоследнейПокупки
ПОМЕСТИТЬ ВтПоследняяДата
ИЗ
ВтСписокТоваров КАК Т
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг.Товары КАК Товары
ПО Т.Номенклатура = Товары.Номенклатура
ГДЕ
Товары.Ссылка.Проведен
СГРУППИРОВАТЬ ПО
Т.Номенклатура
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
Шаг 2: Находим дату предпоследней закупки. Мы снова соединяемся с таблицей документов, но в условии выбора максимума указываем, что дата должна быть строго меньше уже найденной ДатаПоследнейПокупки.
ВЫБРАТЬ
Т.Номенклатура КАК Номенклатура,
МАКСИМУМ(Товары.Ссылка.Дата) КАК ДатаПредпоследнейПокупки
ПОМЕСТИТЬ ВтПредпоследняяДата
ИЗ
ВтПоследняяДата КАК Т
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг.Товары КАК Товары
ПО Т.Номенклатура = Товары.Номенклатура
ГДЕ
Товары.Ссылка.Проведен
И Товары.Ссылка.Дата < Т.ДатаПоследнейПокупки
СГРУППИРОВАТЬ ПО
Т.Номенклатура
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
Шаг 3: Находим дату третьей закупки. Аналогично предыдущему шагу, мы ищем максимум среди дат, которые меньше даты предпоследней закупки.
После получения всех трех дат мы объединяем их в одну таблицу и соединяем с табличной частью документа для получения цен. Рассмотрим преимущества данного метода: он прост в понимании и отладке, а также эффективно использует индексы по полям Номенклатура и Дата.
В языке запросов 1С отсутствуют стандартные оконные функции SQL (например, ROW_NUMBER() OVER...). Однако мы можем имитировать их поведение, соединив таблицу саму с собой. Выясним, как это работает. Посмотрим на пример реализации, где мы считаем количество записей, которые «свежее» текущей.
ВЫБРАТЬ
ВТ_Данные.Номенклатура КАК Номенклатура,
ВТ_Данные.Период КАК Период,
ВТ_Данные.Цена КАК Цена,
СУММА(1) КАК НомерПоПорядку
ПОМЕСТИТЬ ВТ_Ранжированная
ИЗ
ВТ_Данные КАК ВТ_Данные
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Данные КАК ВТ_Данные_Сравнение
ПО ВТ_Данные.Номенклатура = ВТ_Данные_Сравнение.Номенклатура
И ВТ_Данные.Период <= ВТ_Данные_Сравнение.Период
СГРУППИРОВАТЬ ПО
ВТ_Данные.Номенклатура,
ВТ_Данные.Период,
ВТ_Данные.Цена
ИМЕЮЩИЕ
СУММА(1) <= 3
;
Проанализируем ситуацию: если для конкретного товара существует 5 закупок, то для самой последней закупки условие ВТ_Данные.Период <= ВТ_Данные_Сравнение.Период выполнится только 1 раз (только для нее самой). Для предпоследней — 2 раза, и так далее. Таким образом, СУММА(1) выступает в роли счетчика (позиции в рейтинге). В блоке ИМЕЮЩИЕ мы оставляем только те записи, чей порядковый номер не превышает 3.
Важное уточнение: если в базе данных есть документы с абсолютно одинаковой датой и временем, этот метод может вернуть неверное количество строк (счетчик выдаст одинаковые номера). Чтобы избежать этого, в условие соединения и группировки необходимо добавить уникальный идентификатор — поле Ссылка. Сравнение ссылок позволит однозначно упорядочить документы даже внутри одной секунды.
Обращение напрямую к табличным частям документов Документ.ПоступлениеТоваровУслуг.Товары может быть медленным, если база содержит сотни тысяч документов. Рассмотрим более производительный вариант — использование регистров. В типовых конфигурациях информация о закупках фиксируется в регистре накопления Закупки или в регистре сведений Цены номенклатуры.
Работа с регистрами предпочтительнее по следующим причинам:
Номенклатура, Характеристика).Рассмотрим пример запроса к регистру накопления с ограничением выборки:
ВЫБРАТЬ
Закупки.Номенклатура КАК Номенклатура,
Закупки.Период КАК Период,
Закупки.Цена КАК Цена
ИЗ
РегистрНакопления.Закупки КАК Закупки
ГДЕ
Закупки.Номенклатура В (&СписокНоменклатуры)
И Закупки.Период > ДОБАВИТЬКДАТЕ(&ТекущаяДата, МЕСЯЦ, -6)
УПОРЯДОЧИТЬ ПО
Номенклатура,
Период УБЫВ
Обратите внимание, что мы добавили ограничение по периоду (например, за последние 6 месяцев). Это критически важно для производительности: запрос к данным за всю историю существования базы будет выполняться значительно дольше.
При реализации любого из вышеперечисленных методов следует придерживаться нескольких правил, которые помогут избежать зависания системы:
Номенклатура. Это ускорит все последующие соединения.ВЫБРАТЬ РАЗЛИЧНЫЕ на СГРУППИРОВАТЬ ПО, так как это дает оптимизатору запросов больше информации для построения эффективного плана выполнения.Подводя итог, можно сказать, что выбор метода зависит от конкретной задачи. Если вам нужно гарантированно получить 3 записи и данных немного — используйте Метод 1. Если вы стремитесь к более универсальному коду, который легко масштабировать до «топ-5» или «топ-10» — выбирайте Метод 2 с использованием счетчика. И всегда помните о том, что работа с регистрами эффективнее работы с табличными частями документов.