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