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