Как правильно отфильтровать записи в запросе 1С при соединении таблиц, сохранив остальные данные?

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

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

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

Анализ типичной ошибки при использовании ГДЕ

Посмотрим на пример запроса, с которым часто сталкиваются разработчики. Основная проблема заключается в том, что при использовании ЛЕВОЕ СОЕДИНЕНИЕ любое условие в секции ГДЕ, накладываемое на поля правой (присоединяемой) таблицы, фактически превращает соединение во внутреннее для отфильтрованных строк.

Проанализируем фрагмент ошибочного кода:


ГДЕ
    ВЫБОР КОГДА Строка(ВременнаяТаблицаИтог.Удержание.Наименование) = "Удержание по исполнительному документу" Тогда
        ЕСТЬNULL(ВременнаяТаблицаУдержания.ДокументОснование, "") <> ""
    Иначе ЕСТЬNULL(ВременнаяТаблицаУдержания.ДокументОснование, "") = ""
Конец

В данном случае, если удержание не является исполнительным листом, условие ЕСТЬNULL(..., "") = "" заставит систему искать только те строки, для которых нет совпадения во второй таблице. Это приводит к потере данных. Выясним причину: логика 1С (и SQL в целом) обрабатывает секцию ГДЕ после выполнения соединения. Если условие не выполняется, строка просто выбрасывается из итогового результата.

Решение №1: Правильное условие в секции ГДЕ

Чтобы сохранить все записи из ВременнаяТаблицаИтог и при этом отфильтровать только «Исполнительные листы», нам нужно составить логическое выражение, которое будет истинным для всех остальных видов расчетов. Разберем этот подход.

Вместо сложной конструкции ВЫБОР, воспользуемся классической булевой логикой в секции ГДЕ:


ГДЕ
    НЕ ВременнаяТаблицаИтог.Удержание = &ВидУдержанияИсполнительныйЛист
    ИЛИ (ВременнаяТаблицаИтог.Удержание = &ВидУдержанияИсполнительныйЛист 
         И НЕ ВременнаяТаблицаУдержания.ДокументОснование ЕСТЬ NULL)

Проанализируем ситуацию: первая часть условия (до ИЛИ) разрешает вывод любых записей, которые не являются исполнительными листами. Вторая часть (после ИЛИ) разрешает вывод исполнительных листов только в том случае, если для них нашлось соответствие во второй таблице (проверка НЕ ... ЕСТЬ NULL). Это самый простой и эффективный способ исправления логики.

Решение №2: Перенос условий в секцию ПО (Условия соединения)

Еще один профессиональный подход — перенос логики фильтрации непосредственно в условия соединения таблиц. Это позволяет «отсечь» ненужные данные еще на этапе сопоставления таблиц, не затрагивая основной состав строк первой таблицы.

Посмотрим на пример реализации:


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

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

Решение №3: Использование ОБЪЕДИНИТЬ ВСЕ (UNION ALL)

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

Рассмотрим шаги этого метода:

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

ВЫБРАТЬ
    Итог.Сотрудник,
    Итог.Удержание
ИЗ
    ВременнаяТаблицаИтог КАК Итог
ГДЕ
    Итог.Удержание <> &ВидУдержанияИсполнительныйЛист

ОБЪЕДИНИТЬ ВСЕ

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

Важные рекомендации по оптимизации и качеству кода

Чтобы наш запрос работал быстро и не вызывал ошибок в будущем, проанализируем еще несколько важных аспектов:

1. Отказ от сравнения по наименованию. В исходном вопросе использовалось Строка(Удержание.Наименование) = "...". Это крайне не рекомендуется. Во-первых, это медленно (СУБД не может использовать индексы). Во-вторых, при изменении названия удержания в пользовательском режиме отчет сломается. Всегда используйте ссылки через параметры запроса (&ВидУдержания).

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


ИНДЕКСИРОВАТЬ
    ФизическоеЛицо,
    ДокументОснование

3. Проверка на NULL. Для определения наличия записи в правой таблице при левом соединении используйте конструкцию Поле ЕСТЬ NULL. Сравнение с пустой строкой "" через ЕСТЬNULL может работать некорректно для ссылочных типов данных или составных типов, которые часто встречаются в поле ДокументОснование в конфигурациях ЗУП или ERP.

4. Работа с составными типами. Если поле ДокументОснование имеет составной тип, убедитесь, что в обеих таблицах хранятся сопоставимые данные. Иногда полезно использовать функцию ВЫРАЗИТЬ(Поле КАК Документ.ИсполнительныйЛист), чтобы явно указать тип для оптимизатора запросов.

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

← На главную