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