При работе с языком запросов 1С начинающие разработчики часто сталкиваются с ситуацией, когда условие, прописанное в секции ПО (ON), не фильтрует итоговую выборку. Особенно ярко это проявляется, если правая (присоединяемая) таблица пуста. Давайте подробно разберем механизм работы соединений, выясним причину такого поведения и рассмотрим правильные способы решения задачи.
Рассмотрим ситуацию на примере запроса, предоставленного автором темы. В нем выбираются данные из справочника ЗначенияСвойствОбъектов и к ним левым соединением подтягиваются данные из регистра сведений НастройкаАдресовДоставки. Условие по владельцу справочника (левая таблица) ошибочно вынесено в секцию ПО.
ВЫБРАТЬ
ЗначенияСвойствОбъектов.Ссылка КАК СпособДоставки,
НастройкаАдресовДоставки.СпособЗаданияАдресаДоставки,
ЗначенияСвойствОбъектов.Владелец
ИЗ
Справочник.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НастройкаАдресовДоставки КАК НастройкаАдресовДоставки
ПО ЗначенияСвойствОбъектов.Ссылка = НастройкаАдресовДоставки.СпособДоставки
И (ЗначенияСвойствОбъектов.Владелец = &СвойствоСпособДоставки)
Автор заметил, что если регистр НастройкаАдресовДоставки пуст, то условие ЗначенияСвойствОбъектов.Владелец = &СвойствоСпособДоставки как будто игнорируется: в результат попадают абсолютно все записи справочника, независимо от того, кто является их владельцем. Это происходит потому, что логика работы секции ПО принципиально отличается от логики секции ГДЕ.
Проанализируем, как СУБД выполняет ЛЕВОЕ СОЕДИНЕНИЕ. Этот тип соединения гарантирует, что все записи из левой таблицы (основной) попадут в итоговый результат. Секция ПО в данном случае является не фильтром всей выборки, а лишь инструкцией по поиску пары в правой таблице.
Алгоритм работы выглядит следующим образом:
ЗначенияСвойствОбъектов).ПО.NULL.Таким образом, когда мы пишем И (ЗначенияСвойствОбъектов.Владелец = &СвойствоСпособДоставки) внутри ПО, мы буквально говорим системе: "Попробуй найти запись в регистре только для тех элементов справочника, у которых владелец совпадает с параметром. Если владелец другой — просто поставь NULL в колонках регистра, но сам элемент справочника из отчета не удаляй".
Если наша задача — жестко отфильтровать результат запроса, чтобы в нем остались только определенные владельцы из левой таблицы, мы должны использовать секцию ГДЕ. Эта секция применяется к уже сформированному результату соединения. Если условие в ГДЕ не выполняется, вся строка целиком (и левая, и правая части) удаляется из выборки.
Исправленный вариант запроса:
ВЫБРАТЬ
ЗначенияСвойствОбъектов.Ссылка КАК СпособДоставки,
НастройкаАдресовДоставки.СпособЗаданияАдресаДоставки,
ЗначенияСвойствОбъектов.Владелец
ИЗ
Справочник.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НастройкаАдресовДоставки КАК НастройкаАдресовДоставки
ПО ЗначенияСвойствОбъектов.Ссылка = НастройкаАдресовДоставки.СпособДоставки
ГДЕ
ЗначенияСвойствОбъектов.Владелец = &СвойствоСпособДоставки
Теперь запрос отработает корректно: сначала будут отобраны только нужные владельцы, а затем для них (и только для них) будет произведена попытка найти данные в регистре сведений.
Для оптимизации производительности, особенно в сложных запросах с большим объемом данных, "золотым стандартом" разработки на платформе 1С считается использование временных таблиц. Вместо того чтобы соединять всю таблицу справочника с регистром, а потом фильтровать результат, мы можем сначала отобрать нужные записи.
Рассмотрим этот метод по шагам:
// Шаг 1: Отбор нужных данных во временную таблицу
ВЫБРАТЬ
ЗначенияСвойствОбъектов.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_СпособыДоставки
ИЗ
Справочник.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ГДЕ
ЗначенияСвойствОбъектов.Владелец = &СвойствоСпособДоставки
ИНДЕКСИРОВАТЬ ПО
Ссылка;
// Шаг 2: Финальная выборка с соединением
ВЫБРАТЬ
ВТ_СпособыДоставки.Ссылка КАК СпособДоставки,
НастройкаАдресовДоставки.СпособЗаданияАдресаДоставки
ИЗ
ВТ_СпособыДоставки КАК ВТ_СпособыДоставки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НастройкаАдресовДоставки КАК НастройкаАдресовДоставки
ПО ВТ_СпособыДоставки.Ссылка = НастройкаАдресовДоставки.СпособДоставки
Этот подход гарантирует стабильную скорость работы запроса и предсказуемость результата, так как мы полностью исключаем влияние условий соединения на логику фильтрации строк.
При использовании секции ГДЕ нужно быть очень осторожными с полями правой таблицы. Если вы наложите условие на поле из правой таблицы в секции ГДЕ, ваше ЛЕВОЕ СОЕДИНЕНИЕ фактически превратится во ВНУТРЕННЕЕ.
Пример ошибки:
ГДЕ НастройкаАдресовДоставки.СпособЗаданияАдресаДоставки = &Параметр
Так как для записей, которых нет в правой таблице, значение поля будет NULL, а любое сравнение NULL с параметром дает Ложь, все строки левой таблицы без пары будут отсечены. Если вам нужно сохранить все строки левой таблицы, но отфильтровать правую — такое условие обязано находиться в секции ПО.
Выясним, на что еще стоит обратить внимание при работе с соединениями и пустыми таблицами:
ЕСТЬNULL(Поле, ЗначениеЗамены) при выводе данных из правой таблицы, чтобы вместо непонятных пользователю пустых значений выводить осмысленные данные (например, 0 или пустую строку).ПО, должны быть проиндексированы. В регистрах сведений измерения индексируются автоматически, что обеспечивает высокую скорость соединения.ГДЕ делает запрос более понятным для других разработчиков, так как явно разделяет логику связей и логику фильтрации.Проанализировав ситуацию, мы видим, что понимание жизненного цикла выполнения запроса — от формирования пар в ПО до итоговой фильтрации в ГДЕ — является ключом к написанию корректного и быстрого кода в 1С.