Работа с запросами в 1С часто преподносит сюрпризы, особенно когда дело касается отрицаний и сложных типов данных. Ситуация, когда условие НЕ В (&Список) возвращает пустой результат или работает некорректно, является классической проблемой, с которой сталкиваются даже опытные разработчики. В рамках данной статьи мы подробно разберем, почему это происходит, как СУБД обрабатывает такие запросы и как правильно реализовать исключение данных из отчета.
Рассмотрим стандартную задачу: нам необходимо получить обороты по продажам, исключив определенных контрагентов. Автор темы столкнулся с тем, что при использовании условия в параметрах виртуальной таблицы ДокументПродажи.Контрагент НЕ В (&СписокИсключения), запрос выдает пустой результат, хотя данные в базе точно есть. Проанализируем основные причины такого поведения.
1. Проблема трехзначной логики SQL и значений NULL
В языке SQL (на котором базируются запросы 1С) существует три варианта логического результата: ИСТИНА, ЛОЖЬ и UNKNOWN (Неизвестно). Значение NULL при любом сравнении дает UNKNOWN. Условие Поле НЕ В (Значение1, Значение2) технически преобразуется системой в следующую конструкцию:
Поле <> Значение1 И Поле <> Значение2 И Поле <> Значение3...
Если хотя бы в одной строке поле принимает значение NULL, или если в самом списке &СписокИсключения затесалось значение NULL, результат всего выражения для этой строки (или всего запроса) становится неопределенным, и строка отсекается. В контексте задачи автора, если у документа (например, «Отчет о розничных продажах») вообще нет реквизита Контрагент, обращение через точку ДокументПродажи.Контрагент возвращает именно NULL. В итоге условие НЕ В отбрасывает такие записи целиком.
2. Обращение через точку к составным типам
Реквизит ДокументПродажи в регистре накопления «Продажи» обычно имеет составной тип. Когда мы пишем ДокументПродажи.Контрагент в параметрах виртуальной таблицы, платформа 1С вынуждена выполнить неявное левое соединение (LEFT JOIN) со всеми таблицами документов, входящих в этот тип. Если их 20 штук, будет сделано 20 соединений. Если у какого-то типа документа нет реквизита Контрагент, результатом соединения в этом поле будет NULL, что возвращает нас к проблеме, описанной в первом пункте.
Проанализируем структуру типового регистра «Продажи» в УТ 10.3 или аналогичных решениях. В этом регистре уже есть готовое измерение Контрагент. Это ключевой момент. Вместо того чтобы «пробиваться» к данным через регистратор или документ продажи, следует использовать само измерение регистра.
Рассмотрим правильный пример настройки параметров виртуальной таблицы:
ВЫБРАТЬ
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.КоличествоОборот КАК КоличествоОборот
ИЗ
РегистрНакопления.Продажи.Обороты(
&ДатаНач,
&ДатаКон,
Регистратор,
НЕ Контрагент В (&СписокИсключения)
) КАК ПродажиОбороты
Почему это работает лучше?
Контрагент для розничных продаж обычно хранится пустая ссылка (Справочники.Контрагенты.ПустаяСсылка), а не NULL. Пустая ссылка — это конкретное значение, которое корректно сравнивается со списком.Контрагент происходит в разы быстрее, чем выполнение десятков неявных соединений через точку.Если по какой-то причине вам все же необходимо обращаться именно к реквизиту документа (например, если в регистре нет нужного измерения), необходимо явно обрабатывать пустые значения. Выясним, как это сделать с помощью конструкции ГДЕ.
Посмотрим на пример кода:
ГДЕ
ВЫБОР
КОГДА ПродажиОбороты.Регистратор.Контрагент ЕСТЬ NULL ТОГДА ИСТИНА
ИНАЧЕ НЕ ПродажиОбороты.Регистратор.Контрагент В (&СписокИсключения)
КОНЕЦ
В данном случае мы явно говорим системе: «Если контрагента найти не удалось (это розница или другой тип документа), то считаем, что условие фильтрации пройдено (ИСТИНА). В противном случае проверяем вхождение в список». Это позволяет избежать «проглатывания» результатов из-за трехзначной логики SQL.
Для максимально надежной работы на огромных объемах данных, когда условие НЕ В может работать неоптимально, профессиональные разработчики используют ЛЕВОЕ СОЕДИНЕНИЕ с последующей проверкой на NULL. Разберем этот алгоритм по шагам:
Контрагент.ГДЕ оставим только те строки, для которых в правой таблице (ВТ) не нашлось соответствия.Пример реализации:
// Сначала подготавливаем ВТ_Исключения
// ... (код заполнения временной таблицы)
ВЫБРАТЬ
Продажи.Номенклатура,
Продажи.КоличествоОборот
ИЗ
РегистрНакопления.Продажи.Обороты(&ДатаНач, &ДатаКон, , ) КАК Продажи
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Исключения КАК Исключения
ПО Продажи.Контрагент = Исключения.Контрагент
ГДЕ
Исключения.Контрагент ЕСТЬ NULL
Этот метод считается наиболее «чистым» с точки зрения SQL-запросов, так как он полностью контролирует поведение пустых значений и зачастую работает быстрее при больших размерах списка исключений.
Проанализировав проблему, мы выяснили, что основной виновник неработающего условия НЕ В — это неочевидное поведение NULL при обращениях через точку к составным полям. Для корректной работы отчетов следуйте этим правилам:
НЕ В отсекает строки с NULL. Если в данных возможны пустые значения, используйте ЕСТЬNULL или ВЫБОР.ЛЕВОЕ СОЕДИНЕНИЕ и проверку на NULL во второй таблице.NULL, все условие НЕ В может перестать работать.Использование измерения Контрагент напрямую в параметрах виртуальной таблицы — это самый быстрый и правильный путь для типовых конфигураций вроде УТ 10.3. Это не только решит проблему с исчезающими данными, но и значительно ускорит формирование вашего отчета.