При разработке отчетов и обработок в 1С часто требуется получить движения регистра вместе с дополнительными реквизитами или свойствами номенклатуры из регистра сведений ЗначенияСвойствОбъектов. Типичная сложность заключается в том, что если у позиции номенклатуры свойство не заполнено (в регистре сведений физически нет соответствующей записи), запрос может вернуть пустой результат или исключить такие строки из выборки, даже если используется ЛЕВОЕ СОЕДИНЕНИЕ.
Проанализируем причины такого поведения и разберем по шагам, как правильно составить запрос, чтобы номенклатура без свойств не пропадала из итоговой выборки.
Рассмотрим исходную конструкцию, которая часто приводит к ошибке отсечения данных. В секции соединений разработчик связывает регистр накопления с регистром сведений:
ИЗ
РегистрНакопления.ПродажиСебестоимость КАК ПродажиСебестоимость
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ПО ПродажиСебестоимость.Номенклатура = ЗначенияСвойствОбъектов.Объект
ГДЕ
ПродажиСебестоимость.Регистратор = &Документ
И ((ЗначенияСвойствОбъектов.Свойство.Код = &СвойствоКод) ИЛИ (ЗначенияСвойствОбъектов.Свойство ЕСТЬ NULL))
На первый взгляд кажется, что проверка ИЛИ ЗначенияСвойствОбъектов.Свойство ЕСТЬ NULL должна оставить строки, для которых в регистре сведений нет записей. Однако на практике возникают следующие проблемы:
&СвойствоКод), то левое соединение сопоставит строку товара с этой другой записью. В результате поле ЗначенияСвойствОбъектов.Свойство не будет равно NULL, но при этом его код не совпадет с &СвойствоКод. Секция ГДЕ признает условие ложным и полностью удалит строку документа из результата.ГДЕ, фильтрует результат уже после объединения данных. Если условие не пропускает значения NULL со 100% точностью для всех комбинаций строк, левое соединение фактически начинает работать как внутреннее (ВНУТРЕННЕЕ СОЕДИНЕНИЕ).ЗначенияСвойствОбъектов.Свойство.Код заставляет СУБД строить дополнительное скрытое соединение с таблицей плана видов характеристик. Это ухудшает производительность и запутывает логику обработки неопределенных значений.Главное правило реляционных запросов при работе с левыми соединениями: все условия, относящиеся исключительно к правой таблице, должны находиться внутри предложения ПО, а не в секции ГДЕ.
Когда условие накладывается в блоке ПО, СУБД сначала отбирает из правой таблицы только те записи, которые удовлетворяют критерию (в нашем случае — соответствуют конкретному свойству), и только затем присоединяет их к левой таблице. Если у номенклатуры нет такого свойства, строка левой таблицы все равно останется в результате, а все поля из правой таблицы корректно примут значение NULL.
Посмотрим на пример правильного оформления связи:
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ПО ПродажиСебестоимость.Номенклатура = ЗначенияСвойствОбъектов.Объект
И ЗначенияСвойствОбъектов.Свойство = &Свойство
Важный момент: вместо передачи строкового кода и обращения через точку (Свойство.Код) рекомендуется передавать в параметры запроса саму ссылку на свойство (&Свойство). Это исключает лишние соединения таблиц на уровне СУБД.
Измерение Объект в регистре сведений ЗначенияСвойствОбъектов почти всегда имеет составной ссылочный тип данных (номенклатура, контрагенты, договоры и т.д.). Поле Номенклатура в регистре накопления имеет строгий тип СправочникСсылка.Номенклатура.
Когда СУБД сравнивает строго типизированное поле с составным, она вынуждена выполнять проверку типов для каждой строки, что блокирует эффективное использование индексов. Чтобы оптимизировать запрос и ускорить его выполнение, применим конструкцию ВЫРАЗИТЬ:
ПО ПродажиСебестоимость.Номенклатура = ВЫРАЗИТЬ(ЗначенияСвойствОбъектов.Объект КАК Справочник.Номенклатура)
И ЗначенияСвойствОбъектов.Свойство = &Свойство
Так как для номенклатуры без свойства ресурс ЗначенияСвойствОбъектов.Значение вернет NULL, логические проверки вида Значение = ИСТИНА могут вести себя некорректно в зависимости от настроек СУБД. Рекомендуется явно оборачивать такие проверки с помощью функции ЕСТЬNULL или приведения типов:
ВЫБОР
КОГДА ЕСТЬNULL(ЗначенияСвойствОбъектов.Значение, ЛОЖЬ) = ИСТИНА
ТОГДА "Да"
ИНАЧЕ "Нет"
КОНЕЦ КАК Перфорированная
Соберем все рекомендации воедино и посмотрим на рабочий вариант текста запроса:
ВЫБРАТЬ
ПродажиСебестоимость.Регистратор КАК Регистратор,
ПродажиСебестоимость.Номенклатура КАК Номенклатура,
ПродажиСебестоимость.ДокументОприходования КАК ДокументОприходования,
ПродажиСебестоимость.Количество КАК Количество,
ПродажиСебестоимость.Стоимость КАК Стоимость,
ВЫБОР
КОГДА ПродажиСебестоимость.ДокументОприходования ССЫЛКА Документ.КомплектацияНоменклатуры
ТОГДА ПродажиСебестоимость.Стоимость + ПродажиСебестоимость.Количество * 5
ИНАЧЕ ВЫБОР
КОГДА ЕСТЬNULL(ЗначенияСвойствОбъектов.Значение, ЛОЖЬ) = ИСТИНА
ТОГДА ПродажиСебестоимость.Стоимость + ПродажиСебестоимость.Количество * 10
ИНАЧЕ ПродажиСебестоимость.Стоимость
КОНЕЦ
КОНЕЦ КАК СебеСтоимость,
ВЫБОР
КОГДА ПродажиСебестоимость.ДокументОприходования ССЫЛКА Документ.КомплектацияНоменклатуры
ТОГДА "Да"
ИНАЧЕ "Нет"
КОНЕЦ КАК Порезанная,
ВЫБОР
КОГДА ЕСТЬNULL(ЗначенияСвойствОбъектов.Значение, ЛОЖЬ) = ИСТИНА
ТОГДА "Да"
ИНАЧЕ "Нет"
КОНЕЦ КАК Перфорированная
ИЗ
РегистрНакопления.ПродажиСебестоимость КАК ПродажиСебестоимость
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ПО (ПродажиСебестоимость.Номенклатура = ВЫРАЗИТЬ(ЗначенияСвойствОбъектов.Объект КАК Справочник.Номенклатура))
И (ЗначенияСвойствОбъектов.Свойство = &Свойство)
ГДЕ
ПродажиСебестоимость.Регистратор = &Документ
Если после переноса условия в секцию ПО выборка остается пустой для конкретного документа, разберем порядок локализации проблемы:
ВЫБРАТЬ
*
ИЗ
РегистрНакопления.ПродажиСебестоимость КАК ПродажиСебестоимость
ГДЕ
ПродажиСебестоимость.Регистратор = &Документ
&Документ передано некорректное значение (например, строка вместо ссылки или непроведенный экземпляр).&Свойство передается ссылка на элемент плана видов характеристик (ПланыВидовХарактеристик.СвойстваОбъектов), а не строка с наименованием или кодом.