Почему СКД игнорирует условие ЕСТЬ NULL при левом соединении и как это исправить?

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

При разработке отчетов на Системе компоновки данных (СКД) программисты часто сталкиваются с ситуацией, когда запрос, идеально работающий в консоли запросов, выдает неверный результат в отчете. Типичный пример: необходимо вывести данные из левой таблицы, для которых нет соответствий в правой (например, остатки товаров, по которым не было продаж). Мы используем ЛЕВОЕ СОЕДИНЕНИЕ и условие ГДЕ Таблица2.Поле ЕСТЬ NULL, но СКД выводит абсолютно все записи левой таблицы, игнорируя фильтр.

В этой статье мы подробно разберем, почему так происходит, как работает внутренний механизм оптимизации СКД и какие шаги нужно предпринять, чтобы отчет работал корректно.

Причина проблемы: механизм интеллектуальной оптимизации запроса

Важно понимать, что СКД — это не просто инструмент для выполнения SQL-запроса, а мощный генератор. Когда мы нажимаем кнопку «Сформировать», компоновщик анализирует настройки отчета (выбранные поля, группировки, отборы пользователя) и на их основе строит итоговый текст запроса к базе данных.

Проанализируем ситуацию: если в настройках отчета (на вкладке «Настройки») пользователь не выбрал ни одного поля из правой таблицы и не использует их в группировках, СКД считает это соединение «бесполезным» для вывода данных. В целях повышения производительности компоновщик просто вырезает ЛЕВОЕ СОЕДИНЕНИЕ из итогового запроса. Вместе с соединением исчезает и условие ГДЕ Продажи.Номенклатура ЕСТЬ NULL, так как обращаться больше не к чему. В результате мы получаем все записи из основной таблицы без какой-либо фильтрации.

Решение 1: Использование флага «Обязательное»

Самый правильный и надежный способ заставить СКД учитывать поле в запросе, даже если оно не выводится на экран — это настройка ролей полей в наборе данных.

Разберем по шагам, как это сделать:

  1. Откроем схему компоновки данных и перейдем на закладку «Наборы данных».
  2. Найдем в списке полей то поле из правой таблицы, которое используется в условии ЕСТЬ NULL (в нашем примере это Продажи.Номенклатура).
  3. В колонке «Роль» нажмем на кнопку редактирования.
  4. В открывшемся окне установим флажок «Обязательное».

Установка этого флага сообщает компоновщику: «Это поле и таблица, к которой оно относится, должны присутствовать в финальном запросе при любых обстоятельствах, даже если пользователь их не выбрал». Это предотвратит автоматическое удаление соединения.

Решение 2: Принудительное добавление поля в выбранные поля

Если мы не хотим менять схему, можно воспользоваться обходным путем в настройках варианта отчета. Рассмотрим, как это работает:

Достаточно добавить поле Продажи.Номенклатура в список «Выбранные поля» настроек отчета. Как только поле попадает в список вывода, СКД обязана выполнить соединение таблиц, чтобы получить его значение. Однако этот метод неудобен тем, что в отчете появится лишняя пустая колонка. Чтобы ее скрыть, придется возиться с условным оформлением или пользовательскими полями, поэтому Решение 1 остается приоритетным.

Решение 3: Использование временных таблиц

Как показывает практика (сообщение 30 в обсуждении), более сложная структура запроса с использованием временных таблиц помогает жестко зафиксировать логику. Рассмотрим пример кода, который решает задачу через пакетный запрос:


ВЫБРАТЬ
    ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
    ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток
ПОМЕСТИТЬ ВТ_Остатки
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК ТоварыНаСкладахОстатки
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
    Продажи.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
    РегистрНакопления.Продажи КАК Продажи
ГДЕ
    Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Остатки.Номенклатура,
    Остатки.КоличествоОстаток
ИЗ
    ВТ_Остатки КАК Остатки
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Продажи КАК Продажи
        ПО Остатки.Номенклатура = Продажи.Номенклатура
ГДЕ
    Продажи.Номенклатура ЕСТЬ NULL

Когда мы помещаем данные во временные таблицы, СКД сложнее «выкинуть» часть логики, так как структура пакета запросов является более жесткой конструкцией. Однако помните, что даже в финальном запросе пакета СКД может применить оптимизацию, поэтому флаг «Обязательное» все равно рекомендуется использовать для поля связи в последнем запросе пакета.

Решение 4: Замена соединения на условие «НЕ В»

Проанализируем альтернативный подход. Если ваша задача — просто исключить записи, иногда эффективнее отказаться от ЛЕВОЕ СОЕДИНЕНИЕ в пользу вложенного запроса в секции ГДЕ. СКД обычно очень корректно обрабатывает такие конструкции:


ВЫБРАТЬ
    Остатки.Номенклатура,
    Остатки.КоличествоОстаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК Остатки
ГДЕ
    НЕ Остатки.Номенклатура В (
        ВЫБРАТЬ
            Продажи.Номенклатура
        ИЗ
            РегистрНакопления.Продажи КАК Продажи
        ГДЕ
            Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода
    )

Такой подход избавляет от необходимости следить за тем, выведено ли поле правой таблицы в отчет или нет, так как условие В (ВЫБРАТЬ...) является частью фильтра основной таблицы и не оптимизируется компоновщиком путем удаления.

Нюансы работы с функцией ЕСТЬNULL

В ходе обсуждения (сообщения 5-12) предлагались варианты использования функции ЕСТЬNULL(Продажи.Номенклатура, "") = "". Важно помнить:

В СКД эти конструкции работают одинаково с точки зрения оптимизатора. Если СКД решит вырезать таблицу из запроса, то ни ЕСТЬ NULL, ни ЕСТЬNULL() не сработают, так как поля физически не будет в выборке СУБД. Поэтому не стоит искать проблему в синтаксисе — проблема всегда в оптимизации соединений.

Итоги и рекомендации

Выясним причину «глюков» окончательно: это не ошибка платформы, а штатное поведение оптимизатора СКД. Чтобы ваш отчет всегда выводил верные данные, придерживайтесь следующих правил:

  1. Всегда проверяйте итоговый запрос в консоли СКД (не путать с обычной консолью запросов), чтобы увидеть, что реально уходит на сервер базы данных.
  2. Используйте свойство «Обязательное» для полей, участвующих в критически важных соединениях.
  3. При работе с виртуальными таблицами (Остатки, Обороты) старайтесь передавать условия в параметры этих таблиц (&Условие), а не в секцию ГДЕ, чтобы помочь компоновщику правильно построить план запроса.
  4. Если отчет продолжает выдавать странные результаты, попробуйте полностью сбросить настройки пользователя (кнопка «Установить стандартные настройки»), так как закэшированные варианты отчета могут конфликтовать с измененной схемой компоновки.
← На главную