Как в запросе 1С выбрать только один связанный документ с приоритетом (например, проведенный)?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Часто при разработке на платформе 1С возникает задача: связать главную таблицу (например, Документ1) с подчиненной таблицей (Документ2), но так, чтобы в результате на каждую строку первого документа приходилась ровно одна строка второго. При этом связанных документов может быть несколько, и нам нужно выбрать из них "лучший" по определенному условию — например, сначала искать проведенный документ, а если проведенных нет, то брать любой непроведенный.

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

Особенности функции МАКСИМУМ() для ссылочных типов в 1С

Первая мысль, которая приходит в голову — сгруппировать данные по первому документу и использовать агрегатную функцию МАКСИМУМ для ссылки на второй документ. Но как платформа сравнивает ссылки, если это не числа и не даты?

Функции МАКСИМУМ() и МИНИМУМ() для ссылочных типов работают на основе сравнения их внутренних уникальных идентификаторов (УИД / UUID). Сравнение ссылок не учитывает дату создания, номер документа или физический порядок записи в базе данных. Для пользователя результат выборки максимальной ссылки может казаться случайным, но технически алгоритм всегда детерминирован.

Использование конструкции МАКСИМУМ(Док.Ссылка) — это стандартный и вполне корректный паттерн в языке запросов 1С, когда нам нужно извлечь «любую одну» строку из группы дублей, и конкретный порядок при этом не важен. Но как быть с приоритетом проведенных документов?

Алгоритм решения: Метод приоритетов и временные таблицы

Чтобы гарантированно получить нужный результат (приоритет проведенного документа перед непроведенным) и избежать "размножения" строк при левом соединении, мы применим трехшаговый алгоритм с использованием временных таблиц.

В отличие от T-SQL, где мы могли бы использовать конструкцию коррелированного подзапроса вроде SELECT TOP 1 ... ORDER BY ... прямо в списке выборки, язык запросов 1С не поддерживает ПЕРВЫЕ 1 во вложенных запросах на уровне полей. Поэтому подход с группировкой и временными таблицами остается единственным архитектурно верным способом.

Разберем по шагам алгоритм, который поможет нам решить задачу:

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

Реализация метода в коде

Посмотрим на пример пакетного запроса, реализующего описанную выше логику. Данный код универсален и легко адаптируется под ваши объекты конфигурации.


ВЫБРАТЬ
    Док1.Ссылка КАК Документ1,
    Док2.Ссылка КАК Документ2,
    ВЫБОР
        КОГДА Док2.Проведен ТОГДА 2
        ИНАЧЕ 1
    КОНЕЦ КАК Приоритет
ПОМЕСТИТЬ ВТ_ВсеСвязи
ИЗ
    Документ.Документ1 КАК Док1
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.Документ2 КАК Док2
        ПО Док1.Ссылка = Док2.ДокументОснование
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_ВсеСвязи.Документ1 КАК Документ1,
    МАКСИМУМ(ВТ_ВсеСвязи.Приоритет) КАК МаксимальныйПриоритет
ПОМЕСТИТЬ ВТ_МаксПриоритет
ИЗ
    ВТ_ВсеСвязи КАК ВТ_ВсеСвязи
СГРУППИРОВАТЬ ПО
    ВТ_ВсеСвязи.Документ1
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_МаксПриоритет.Документ1 КАК Документ1,
    МАКСИМУМ(ВТ_ВсеСвязи.Документ2) КАК Документ2
ИЗ
    ВТ_МаксПриоритет КАК ВТ_МаксПриоритет
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ВсеСвязи КАК ВТ_ВсеСвязи
        ПО ВТ_МаксПриоритет.Документ1 = ВТ_ВсеСвязи.Документ1
            И ВТ_МаксПриоритет.МаксимальныйПриоритет = ВТ_ВсеСвязи.Приоритет
СГРУППИРОВАТЬ ПО
    ВТ_МаксПриоритет.Документ1

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

Почему не стоит писать огромные "плоские" запросы

Часто разработчики пытаются решить подобную задачу объединением множества левых соединений, вложенных конструкций ВЫБОР и проверок ЕСТЬNULL в одном огромном запросе. Это серьезный антипаттерн в 1С.

Выясним причину, почему такие гигантские запросы вредны:

Правильный подход — дробление сложного запроса на логические блоки с помощью временных таблиц (как мы продемонстрировали выше). Сначала подготовьте компактные справочники соответствий «один к одному» (например, "Документ1 — Один лучший Документ2") в отдельные ВТ. И только в самом конце выполните простой, плоский запрос, соединив основную выборку с этими подготовленными ВТ. Это не только сделает код изящным, но и значительно повысит производительность системы.

← На главную