Почему запрос в 1С с ЛЕВОЕ СОЕДИНЕНИЕ и ОБЪЕДИНИТЬ ВСЕ не возвращает данные и как это исправить

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

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

Исходная ситуация и анализ проблемы

Рассмотрим исходный программный код, который вызвал затруднения у автора темы:


Запрос = Новый Запрос;
Запрос.Текст="ВЫБРАТЬ
             |    Номенклатура.Ссылка,
             |    ЗначенияСвойствОбъектов.Свойство,
             |    ЗначенияСвойствОбъектов.Значение,
             |    ЗначенияСвойствОбъектов.Объект.Ссылка КАК Объект
             |ИЗ
             |    Справочник.Номенклатура КАК Номенклатура
             |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
             |        ПО Номенклатура.Ссылка = ЗначенияСвойствОбъектов.Объект.Ссылка
             |ГДЕ
             |    ЗначенияСвойствОбъектов.Значение В ИЕРАРХИИ(&Значение)
             |    И Номенклатура.Ссылка В ИЕРАРХИИ(&СсылкаНом)
             |
             |ОБЪЕДИНИТЬ ВСЕ
             |
             |ВЫБРАТЬ
             |    Номенклатура.Ссылка,
             |    0,
             |    0,
             |    NULL
             |ИЗ
             |    Справочник.Номенклатура КАК Номенклатура";

Запрос.УстановитьПараметр("Значение", Справочники.ЗначенияСвойствОбъектов.НайтиПоНаименованию(Стр3));
Запрос.УстановитьПараметр("Объект", НайденнаяСсылка);

При выполнении этого кода автор столкнулся с тем, что первая часть запроса (до оператора ОБЪЕДИНИТЬ ВСЕ) не возвращает никаких данных, а во второй части возвращаются только пустые строки с NULL в колонке объекта. Давайте разберем по шагам, почему так происходит, и исправим все ошибки.

---

Шаг 1. Исправление опечатки в параметрах запроса

Первое, что бросается в глаза при анализе кода — это несоответствие имен параметров в тексте запроса и в коде их установки. Проанализируем эти строки:

В тексте запроса мы видим условие:

Номенклатура.Ссылка В ИЕРАРХИИ(&СсылкаНом)

А в коде инициализации параметров передается совершенно другое имя:

Запрос.УстановитьПараметр("Объект", НайденнаяСсылка);

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

Решение: Имена параметров в коде и в тексте запроса должны строго совпадать. Мы должны передавать именно &СсылкаНом:

Запрос.УстановитьПараметр("СсылкаНом", НайденнаяСсылка); ---

Шаг 2. Ловушка ЛЕВОГО СОЕДИНЕНИЯ в секции ГДЕ

Это самая распространенная и опасная логическая ошибка при составлении SQL-запросов в 1С. Выясним, почему первая часть запроса не возвращает данные, даже если параметры установлены верно.

В запросе используется ЛЕВОЕ СОЕДИНЕНИЕ между справочником Номенклатура и регистром сведений ЗначенияСвойствОбъектов. Смысл левого соединения в том, чтобы выбрать всю номенклатуру, а если у какой-то позиции нет свойств в регистре, то заполнить эти поля значениями NULL.

Однако далее в секции ГДЕ накладывается условие на правую (присоединяемую) таблицу:

ЗначенияСвойствОбъектов.Значение В ИЕРАРХИИ(&Значение)

Проанализируем, как СУБД обрабатывает это условие:

  1. Для номенклатуры, у которой в регистре нет записи, поле ЗначенияСвойствОбъектов.Значение принимает значение NULL.
  2. СУБД проверяет условие: NULL В ИЕРАРХИИ (&Значение).
  3. Результат любого сравнения с NULL всегда равен ложному значению (Ложь).
  4. В итоге все записи номенклатуры, у которых нет данного свойства, отсекаются фильтром.

Таким образом, наше ЛЕВОЕ СОЕДИНЕНИЕ превратилось во ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Если у выбранной группы номенклатуры &СсылкаНом нет ни одного установленного свойства со значением &Значение, первая часть запроса гарантированно вернет абсолютно пустой результат!

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


// Неправильно:
ПО Номенклатура.Ссылка = ЗначенияСвойствОбъектов.Объект
ГДЕ ЗначенияСвойствОбъектов.Значение В ИЕРАРХИИ(&Значение)

// Правильно:
ПО Номенклатура.Ссылка = ЗначенияСвойствОбъектов.Объект
    И ЗначенияСвойствОбъектов.Значение В ИЕРАРХИИ(&Значение)

---

Шаг 3. Устранение неявных соединений (обращение через точку)

Обратим внимание на условие связи в исходном запросе:

ПО Номенклатура.Ссылка = ЗначенияСвойствОбъектов.Объект.Ссылка

И на выбор поля:

ЗначенияСвойствОбъектов.Объект.Ссылка КАК Объект

В регистре сведений ЗначенияСвойствОбъектов измерение Объект имеет составной тип данных (оно может ссылаться на справочники «Номенклатура», «ХарактеристикиНоменклатуры», «Документы» и т.д.). Когда мы пишем Объект.Ссылка, платформа 1С на уровне СУБД выполняет неявное соединение (LEFT JOIN) со всеми таблицами, входящими в этот составной тип. Это приводит к значительному падению производительности запроса.

Поскольку реквизит Объект сам по себе является ссылкой, обращение к свойству .Ссылка избыточно. Ссылка уже содержит уникальный идентификатор записи.

Решение: Убираем обращение через точку. Пишем просто ЗначенияСвойствОбъектов.Объект.

---

Шаг 4. Оптимизация структуры запроса и избавление от ОБЪЕДИНИТЬ ВСЕ

Проанализируем вторую часть запроса:


ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
    Номенклатура.Ссылка,
    0,
    0,
    NULL
ИЗ
    Справочник.Номенклатура КАК Номенклатура

Здесь разработчик пытается выбрать вообще весь справочник Номенклатура без каких-либо отборов по иерархии &СсылкаНом. Мало того, что это приведет к чтению миллионов записей на реальной базе данных, так еще и создаст дубли: товары, у которых свойства заполнены, выгрузятся дважды (один раз с заполненными свойствами, второй раз — с нулями).

Если наша задача — получить список номенклатуры по иерархии &СсылкаНом и вывести значения их свойств (а если свойств нет — вывести значения по умолчанию), то конструкция ОБЪЕДИНИТЬ ВСЕ вообще не требуется! Мы можем решить эту задачу элегантно с помощью одного левого соединения и функции ЕСТЬNULL.

---

Окончательный вариант решения

Объединив все исправления, мы получаем чистый, быстрый и логически верный запрос. Рассмотрим итоговый код:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    Номенклатура.Ссылка КАК Ссылка,
    |    ЕСТЬNULL(ЗначенияСвойствОбъектов.Свойство, 0) КАК Свойство,
    |    ЕСТЬNULL(ЗначенияСвойствОбъектов.Значение, 0) КАК Значение,
    |    ЗначенияСвойствОбъектов.Объект КАК Объект
    |ИЗ
    |    Справочник.Номенклатура КАК Номенклатура
    |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
    |        ПО Номенклатура.Ссылка = ЗначенияСвойствОбъектов.Объект
    |            И ЗначенияСвойствОбъектов.Значение В ИЕРАРХИИ(&Значение)
    |ГДЕ
    |    Номенклатура.Ссылка В ИЕРАРХИИ(&СсылкаНом)";

// Выполняем поиск значения свойства на стороне сервера приложений
ЗначениеСвойства = Справочники.ЗначенияСвойствОбъектов. НайтиПоНаименованию(Стр3);

// Перед установкой параметров полезно сделать проверку на заполненность
Если ЗначениеСвойства.Пустая() Тогда
    ВызватьИсключение "Не найдено свойство с наименованием: " + Стр3;
КонецЕсли;

Запрос.УстановитьПараметр("Значение", ЗначениеСвойства);
Запрос.УстановитьПараметр("СсылкаНом", НайденнаяСсылка);

РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

Пока Выборка.Следующий() Цикл
    // Обработка результатов
КонецЦикла;

Что изменилось в оптимизированном решении?

Используйте этот подход при проектировании отчетов и обработок, связывающих справочники с регистрами сведений, чтобы ваши запросы всегда работали корректно и быстро!

← На главную