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