Как правильно установить отбор в запросе при левом соединении двух таблиц

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

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

Проблема превращения левого соединения во внутреннее

Рассмотрим ситуацию, с которой сталкиваются многие начинающие разработчики. Допустим, нам нужно получить все записи из ТаблицаДокументов1 и присоединить к ним данные из ТаблицаДокументов2, но только те, где статус равен определенному значению. Типичная ошибка — установка условия в секции ГДЕ.

Проанализируем, что происходит на уровне СУБД, когда мы пишем такой запрос:


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

В этом случае левое соединение фактически превращается во внутреннее (INNER JOIN). Выясним причину: при левом соединении, если для записи из левой таблицы не найдено соответствие в правой, все поля правой таблицы заполняются значением NULL. Условие Док2.Проведен = ИСТИНА в секции ГДЕ проверяется уже после выполнения соединения. Поскольку сравнение NULL = ИСТИНА всегда дает «Ложь», все строки, где правая таблица не состыковалась, будут отсечены. В итоге мы потеряем те записи из Док1, для которых нет проведенных документов в Док2, что нарушает саму суть левого соединения.

Решение 1: Условия в секции связи ПО (JOIN)

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


ВЫБРАТЬ
    Док1.Ссылка,
    Док2.Сумма
ИЗ
    Документ.РеализацияТоваровУслуг КАК Док1
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг КАК Док2
        ПО Док1.Контрагент = Док2.Контрагент
        И Док2.Проведен = ИСТИНА
        И Док2.Дата МЕЖДУ &НачалоПериода И &КонецПериода

При таком подходе СУБД сначала применит фильтры к правой таблице в момент сопоставления строк, и если подходящая запись не будет найдена, строка из левой таблицы все равно останется в результате, а поля правой таблицы просто будут содержать NULL. Это корректный с точки зрения логики способ, однако он имеет ограничения по производительности при работе с большими объемами данных или сложными условиями (например, при использовании разыменования через точку или функций).

Решение 2: Использование вложенных запросов

Мы можем выделить выборку из второй таблицы в отдельный вложенный запрос и наложить все необходимые отборы внутри него. Рассмотрим этот вариант:


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

Этот метод делает запрос более читаемым, так как логика подготовки данных отделена от логики их соединения. Однако у вложенных запросов есть существенный минус: оптимизатор СУБД не всегда может корректно построить план выполнения, особенно если вложенный запрос возвращает много строк — поможет анализ производительности запросов и планов выполнения СУБД. Оптимизатор вынужден оценивать количество записей «на лету», что часто приводит к выбору неоптимального способа соединения (например, замене быстрого поиска по индексу на полное сканирование таблицы).

Решение 3: Временные таблицы — «Золотой стандарт» разработки

Наиболее правильным, масштабируемым и производительным методом является использование временных таблиц (ВТ). Проанализируем по шагам, почему это работает лучше всего:

  1. Подготовка данных: Мы выбираем только нужные поля и накладываем все фильтры для каждой таблицы отдельно, помещая результат во временные таблицы.
  2. Статистика: При создании временной таблицы платформа 1С передает СУБД точную информацию о количестве записей. Это позволяет оптимизатору построить идеальный план выполнения финального запроса.
  3. Индексация: Мы можем явно указать индекс для временной таблицы по полям связи, что критично для скорости соединения на больших объемах.

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


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

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

// Финальное соединение подготовленных данных
ВЫБРАТЬ
    Т1.Ссылка,
    Т2.Сумма
ИЗ
    ВТ_Документы1 КАК Т1
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Документы2 КАК Т2
        ПО Т1.Контрагент = Т2.Контрагент

Особенности работы с виртуальными таблицами

Важно помнить, что если в качестве источников данных выступают виртуальные таблицы (например, Остатки или Обороты), установка отборов в секции ГДЕ или ПО является грубой ошибкой. В этом случае СУБД сначала рассчитает итоги по всему регистру, а только потом отфильтрует их. Для виртуальных таблиц отборы следует передавать исключительно в параметры виртуальной таблицы.


// Правильно: отбор внутри параметров
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура = &НужнаяНоменклатура)

Влияние на различные СУБД

При выборе метода стоит учитывать особенности вашей серверной среды. Для Microsoft SQL Server вложенные запросы зачастую обрабатываются неплохо, так как его оптимизатор достаточно мощен — для этого подойдёт выгрузка данных 1С во внешние СУБД. Однако для PostgreSQL использование временных таблиц является практически обязательным требованием для обеспечения стабильной производительности. Разделение сложного запроса на последовательные простые этапы с использованием ВТ позволяет PostgreSQL строить предсказуемые и быстрые планы выполнения.

Также использование временных таблиц упрощает работу механизма RLS (ограничение прав доступа на уровне записей) — есть готовый архитектурный аудит логики запросов и механизмов RLS. Когда данные предварительно отобраны во временную таблицу, проверка прав происходит более прозрачно, что снижает нагрузку на сервер при финальном объединении данных.

Резюме по лучшей практике

Подводя итог, можно составить следующий алгоритм действий для построения качественного запроса с левым соединением:

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

← На главную