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