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