Как выбрать несколько последних документов по каждому контрагенту в одном запросе 1С

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

В практике разработки на платформе 1С часто возникает задача, которую невозможно решить простым использованием конструкции ПЕРВЫЕ. Например, когда нам нужно получить не просто 4 последних заказа в целом по системе, а по 4 последних заказа для каждого контрагента. В типовых конфигурациях, таких как УПП или комплексная автоматизация, где объем данных в регистре «Продажи» может исчисляться миллионами записей, выбор неоптимального метода может привести к фатальному падению производительности.

В этой статье мы подробно разберем несколько проверенных способов решения данной задачи: от классических запросов с соединениями до современных оконных функций и механизмов СКД. Рассмотрим плюсы и минусы каждого подхода, чтобы вы могли выбрать наиболее подходящий для вашей базы данных.

Метод 1: Левое соединение с подсчетом количества «старших» записей

Это классический способ, который часто применялся до появления оконных функций. Суть метода заключается в том, чтобы соединить таблицу саму с собой по условию «Дата больше, чем в текущей строке». Проанализируем логику: если для текущей записи мы найдем меньше 4-х записей с более поздней датой, значит, текущая запись входит в четверку последних.

Рассмотрим пример кода для реализации этого алгоритма:


ВЫБРАТЬ
    Заказы1.Ссылка КАК Ссылка,
    Заказы1.Дата КАК Дата,
    Заказы1.Контрагент КАК Контрагент
ПОМЕСТИТЬ ВТ_ВсеЗаказы
ИЗ
    Документ.ЗаказПокупателя КАК Заказы1;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    т1.Контрагент,
    т1.Ссылка,
    т1.Дата
ИЗ
    ВТ_ВсеЗаказы КАК т1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ВсеЗаказы КАК т2
    ПО т1.Контрагент = т2.Контрагент
    И т1.Дата < т2.Дата
ГРУППИРОВАТЬ ПО
    т1.Контрагент,
    т1.Ссылка,
    т1.Дата
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ т2.Ссылка) < 4
УПОРЯДОЧИТЬ ПО
    т1.Контрагент,
    т1.Дата УБЫВ

Важный нюанс: Этот метод довольно ресурсоемок. При большом количестве заказов по одному контрагенту соединение «многие ко многим» внутри группы создаст огромную промежуточную таблицу в памяти SQL-сервера. Используйте его только в том случае, если количество документов по каждому контрагенту невелико, либо предварительно ограничивайте выборку периодом в первой временной таблице.

Метод 2: Коррелированные подзапросы

Этот способ часто вызывает споры среди разработчиков. С одной стороны, 1С не рекомендует использовать конструкцию В (ВЫБРАТЬ ПЕРВЫЕ...) внутри цикла по основной таблице, с другой — современные оптимизаторы СУБД (особенно MS SQL Server) отлично справляются с такими запросами, превращая их в эффективные операции Apply.

Разберем, как выглядит такой запрос:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ПродажиОбороты.Контрагент КАК Контрагент
ПОМЕСТИТЬ ВТ_СписокКонтрагентов
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНач, &ДатаКон, , ) КАК ПродажиОбороты;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ.Контрагент,
    ПоследниеЗаказы.Заказ
ИЗ
    ВТ_СписокКонтрагентов КАК ВТ
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи КАК ПоследниеЗаказы
    ПО ПоследниеЗаказы.Регистратор В
        (ВЫБРАТЬ ПЕРВЫЕ 4
            Прод.Регистратор
        ИЗ
            РегистрНакопления.Продажи КАК Прод
        ГДЕ
            Прод.Контрагент = ВТ.Контрагент
        УПОРЯДОЧИТЬ ПО
            Прод.Период УБЫВ)

Преимущество этого метода в его лаконичности. Однако стоит учитывать, что в некоторых версиях платформы 1С и СУБД коррелированный запрос может выполняться медленно, если по полю Контрагент в физической таблице регистра отсутствует эффективный индекс.

Метод 3: Использование оконных функций (для платформы 8.3.20+)

Если ваша информационная база работает на современной платформе (8.3.20 и выше), забудьте о сложных соединениях. Теперь в языке запросов 1С поддерживаются оконные функции, которые идеально подходят для задачи «N лучших в группе».

Проанализируем пример с использованием функции ROW_NUMBER():


ВЫБРАТЬ
    Продажи.Контрагент,
    Продажи.Регистратор,
    Продажи.Период,
    ПОРЯДОК_НОМЕР() ОБРАБОТКА ПО (Продажи.Контрагент УПОРЯДОЧИТЬ ПО Продажи.Период УБЫВ) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_НумерованныеПродажи
ИЗ
    РегистрНакопления.Продажи КАК Продажи;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    т.Контрагент,
    т.Регистратор КАК Заказ,
    т.Период
ИЗ
    ВТ_НумерованныеПродажи КАК т
ГДЕ
    т.НомерСтроки <= 4

Почему это лучший вариант? Оконная функция выполняется СУБД за один проход по индексу. Это самый быстрый и современный способ, который не нагружает сервер лишними соединениями и временными таблицами.

Метод 4: Последовательный выбор максимумов (метод «нарезки»)

Для высоконагруженных систем на старых платформах часто применяют метод «выбивания» максимальных значений через временные таблицы. Сначала находим самый последний заказ (максимум по дате), затем во вторую таблицу ищем заказ, дата которого меньше максимальной, и так далее.

Рассмотрим по шагам выборку первых двух заказов (по аналогии можно расширить до четырех):

  1. Создаем ВТ1, куда помещаем МАКСИМУМ(Дата) по каждому контрагенту.
  2. Создаем ВТ2, соединяя основную таблицу с ВТ1 и выбирая МАКСИМУМ(Дата), которая строго меньше даты из ВТ1.
  3. Объединяем результаты всех временных таблиц через ОБЪЕДИНИТЬ ВСЕ.

Несмотря на громоздкость кода, этот метод работает очень стабильно, так как использует простые индексы и оператор MAX, который в SQL отрабатывает мгновенно.

Метод 5: Использование Системы компоновки данных (СКД)

Если задача стоит в рамках формирования отчета, мы можем переложить всю сложность на механизмы СКД. Для этого выясним два способа настройки:

  1. Ограничение в настройках группировки: В СКД для конкретной группировки (например, по Контрагенту) на закладке «Выборка» можно указать «Количество записей» = 4. При этом важно установить правильную сортировку внутри группировки по дате убывающе.
  2. Связи наборов данных: Создаем два набора данных. Первый выбирает контрагентов. Второй — документы с конструкцией ПЕРВЫЕ 4. В связях наборов передаем Контрагент как параметр. СКД сама выполнит отдельный быстрый запрос для каждого контрагента, что часто оказывается быстрее одного огромного сложного запроса.

Рекомендации по оптимизации производительности

Независимо от выбранного метода, обратите внимание на следующие моменты:

Подведем итог: если платформа позволяет — используйте ПОРЯДОК_НОМЕР(). Если база огромная и старая — используйте коррелированные подзапросы или метод последовательных максимумов. Для отчетов идеальным выбором станет СКД с ограничением количества записей в группировке.

← На главную