Как в запросе 1С получить последнюю реализацию по заказу покупателя?

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

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

Проблема автора заключалась в том, что при левом соединении таблицы заказов с таблицей реализаций в результат попадали все реализации по заказу. Попытка использовать конструкцию ВЫБРАТЬ ПЕРВЫЕ 1 во вложенном запросе не дала нужного эффекта, так как без правильной связи с внешним запросом она возвращает одну и ту же случайную запись для всех строк.

Проанализируем два наиболее эффективных способа получить именно последнюю запись из подчиненной таблицы.

Решение 1: Использование временных таблиц и агрегации

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

Рассмотрим алгоритм по шагам:

  1. Подготовка данных. В первую временную таблицу помещаем все реализации, связанные с нужными нам заказами. На этом этапе важно отобрать только необходимые поля (ссылку на документ, дату и поле связи — заказ) и применить первичные фильтры, чтобы уменьшить объем обрабатываемых данных.
  2. Поиск последних документов. Создаем вторую временную таблицу. В нее мы группируем данные из первой ВТ по полю связи (Заказ) и с помощью агрегатной функции МАКСИМУМ() находим последнюю по дате ссылку на документ реализации для каждого заказа.
  3. Сбор итогового результата. В основном, финальном запросе мы соединяем нашу исходную таблицу заказов с второй временной таблицей (где хранятся ссылки на последние реализации). Таким образом, для каждого заказа мы получаем только одну, самую последнюю, реализацию.

Посмотрим на пример кода:

Предположим, у нас есть заказы, и мы хотим для каждого из них найти последнюю реализацию.


// Пакет 1: Собираем все реализации по нужным нам заказам
ВЫБРАТЬ
    РеализацияТоваровУслуг.Ссылка КАК ДокументРеализации,
    РеализацияТоваровУслуг.Сделка КАК Заказ,
    РеализацияТоваровУслуг.Дата КАК ДатаРеализации
ПОМЕСТИТЬ ВТ_ВсеРеализации
ИЗ
    Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ГДЕ
    РеализацияТоваровУслуг.Проведен = ИСТИНА
    И РеализацияТоваровУслуг.Сделка В (&СписокЗаказов); // Ограничиваем выборку для производительности

ИНДЕКСИРОВАТЬ ПО
    Заказ;

////////////////////////////////////////////////////////////////////////////////
// Пакет 2: Находим последнюю реализацию для каждого заказа
ВЫБРАТЬ
    ВТ_ВсеРеализации.Заказ,
    МАКСИМУМ(ВТ_ВсеРеализации.ДокументРеализации) КАК ПоследняяРеализация // МАКСИМУМ по ссылке работает, т.к. она включает дату
ПОМЕСТИТЬ ВТ_ПоследниеРеализации
ИЗ
    ВТ_ВсеРеализации КАК ВТ_ВсеРеализации
СГРУППИРОВАТЬ ПО
    ВТ_ВсеРеализации.Заказ;

ИНДЕКСИРОВАТЬ ПО
    Заказ;

////////////////////////////////////////////////////////////////////////////////
// Пакет 3: Основной запрос для вывода данных
ВЫБРАТЬ
    Заказы.Ссылка КАК ЗаказПокупателя,
    Заказы.Дата,
    Заказы.СуммаДокумента,
    ПоследниеРеализации.ПоследняяРеализация,
    ПоследниеРеализации.ПоследняяРеализация.Дата КАК ДатаПоследнейРеализации,
    ПоследниеРеализации.ПоследняяРеализация.СуммаДокумента КАК СуммаПоследнейРеализации
ИЗ
    Документ.ЗаказПокупателя КАК Заказы
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПоследниеРеализации КАК ПоследниеРеализации
        ПО Заказы.Ссылка = ПоследниеРеализации.Заказ
ГДЕ
    Заказы.Ссылка В (&СписокЗаказов);

Важные моменты этого подхода:

Решение 2: Коррелированный подзапрос в условии соединения

Этот способ более компактный и не требует создания временных таблиц. Он заключается в использовании вложенного запроса прямо в условии соединения ЛЕВОЕ СОЕДИНЕНИЕ ... ПО. Такой подзапрос называется коррелированным, потому что он для своего выполнения использует данные из внешнего запроса.

Ключевая особенность синтаксиса 1С: конструкция УПОРЯДОЧИТЬ ПО внутри вложенного запроса разрешена только в связке с ВЫБРАТЬ ПЕРВЫЕ N. Именно это сочетание позволяет нам для каждой строки внешнего запроса выбрать одну, самую "верхнюю" по сортировке, запись из подчиненной таблицы.

Проанализируем пример кода:

Возьмем две гипотетические таблицы ВТ1 (основная) и ВТ2 (подчиненная). Нам нужно к каждой строке ВТ1 присоединить самую последнюю по дате строку из ВТ2.


ВЫБРАТЬ
    ВТ1.Поле1,
    ВТ1.Поле2,
    ВТ2_Последняя.НужноеПоле // Поле из последней записи ВТ2
ИЗ
    ВТ1 КАК ВТ1
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ2 КАК ВТ2_Последняя
        ПО ВТ1.ПолеСвязи = ВТ2_Последняя.ПолеСвязи
        И ВТ2_Последняя.Ссылка В
            (ВЫБРАТЬ ПЕРВЫЕ 1
                Т.Ссылка
            ИЗ
                ВТ2 КАК Т
            ГДЕ
                Т.ПолеСвязи = ВТ1.ПолеСвязи // Это и есть корреляция - связь с внешним запросом
            УПОРЯДОЧИТЬ ПО
                Т.Дата УБЫВ, // Сортируем по дате, чтобы самая последняя была первой
                Т.Ссылка УБЫВ) // Доп. сортировка для уникальности, если даты совпадают

Разберем, как это работает:

  1. Основной запрос берет очередную строку из таблицы ВТ1.
  2. Для этой строки выполняется левое соединение с таблицей ВТ2.
  3. Условие соединения состоит из двух частей:
    • ВТ1.ПолеСвязи = ВТ2_Последняя.ПолеСвязи — стандартное соединение по ключу.
    • И ВТ2_Последняя.Ссылка В (...) — дополнительный фильтр, который и выполняет всю магию.
  4. Вложенный (коррелированный) подзапрос выполняется для каждой строки ВТ1. Он получает значение ВТ1.ПолеСвязи, отбирает все связанные записи из ВТ2, сортирует их по дате в порядке убывания (УБЫВ) и возвращает ссылку (Ссылка) только одной, самой первой из отсортированного списка, записи.
  5. В итоге, для каждой строки ВТ1 к ней присоединяется только одна, самая последняя по дате, запись из ВТ2.

Этот метод элегантен и краток, но на больших объемах данных может работать медленнее, чем подход с временными таблицами, так как подзапрос потенциально выполняется много раз.

Итог: какой способ выбрать?

Оба метода решают поставленную задачу. Выбор зависит от конкретной ситуации:

В любом случае, понимание обоих механизмов является важной частью инструментария любого разработчика 1С.

← На главную