Почему рабочий запрос возвращает пустой результат в СКД и как это исправить?

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

Каждый разработчик на платформе 1С:Предприятие рано или поздно сталкивается с парадоксальной ситуацией: написан запрос для поиска дублей или анализа данных, в Консоли запросов он выполняется без ошибок и возвращает сотни строк, но при переносе в Систему компоновки данных (СКД) отчет выводит лишь пустую таблицу — только заголовки колонок без единой строки данных. При этом в настройках отчета нет ни параметров, ни скрытых отборов, а структура группировок кажется абсолютно верной.

В этой статье мы подробно разберем, почему возникает эта проблема, как особенности оптимизатора СКД влияют на выполнение запросов СУБД и каким образом рефакторинг запроса с использованием конструкции ОБЪЕДИНИТЬ ВСЕ позволяет полностью решить проблему пустых результатов и колоссально увеличить производительность отчета.

Анализ исходной проблемы и структуры запроса

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


ВЫБРАТЬ
    Контрагенты.Ссылка КАК Оригинал,
    Контрагенты1.Ссылка КАК Дубль,
    ВЫБОР
        КОГДА Контрагенты.Наименование = Контрагенты1.Наименование
            ТОГДА "Совпадает полное наименование"
        КОГДА Контрагенты.КороткоеНаименование = Контрагенты1.КороткоеНаименование
            ТОГДА "Совпадает короткое наименование"
        ИНАЧЕ "Совпадает ИНН"
    КОНЕЦ КАК ТипДублирования
ИЗ
    Справочник.Контрагенты КАК Контрагенты
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты1
        ПО Контрагенты.Ссылка <> Контрагенты1.Ссылка
            И Контрагенты.Код < Контрагенты1.Код
            И (Контрагенты.Наименование = Контрагенты1.Наименование
                ИЛИ Контрагенты.ИНН = Контрагенты1.ИНН
                ИЛИ Контрагенты.КороткоеНаименование = Контрагенты1.КороткоеНаименование)
ГДЕ
    НЕ Контрагенты.ПометкаУдаления
    И НЕ Контрагенты1.ПометкаУдаления

В Консоли запросов этот код работает и находит дубликаты. Однако время его выполнения составляет порядка 30 секунд. При переносе в СКД отчет формирует пустоту. Чтобы понять природу этого явления, нам необходимо заглянуть во внутренние механизмы работы платформы 1С.

Причина 1. Как оптимизатор СКД переписывает запросы

Главное заблуждение разработчиков заключается в том, что СКД якобы отправляет в СУБД ровно тот запрос, который написан в схеме компоновки данных. Это не так. По умолчанию в наборе данных включен флаг Автозаполнение. СКД анализирует настройки отчета (какие поля выбраны пользователем, какие используются в группировках, какие отборы применены) и на основе этого динамически перестраивает исходный SQL-текст.

Когда мы выполняем самосоединение (таблица соединяется сама с собой), оптимизатор СКД может совершить логическую ошибку:

  1. Если в выбранных полях отчета или в группировках не используются ключевые поля из правой таблицы (например, Контрагенты1.Ссылка), СКД может посчитать это соединение «избыточным» и исключить его поля из финальной выборки для оптимизации трафика.
  2. В результате этого ломается условие связи Контрагенты.Ссылка <> Контрагенты1.Ссылка, и СУБД возвращает пустой набор записей.

Для предотвращения такого поведения в настройках набора данных СКД на вкладке Поля для всех ключевых полей, участвующих в связях (даже если они напрямую не выводятся в отчет), следует принудительно устанавливать свойство Обязательное.

Причина 2. Использование оператора ИЛИ в условиях связи (Ограничение СУБД)

Вторая и наиболее критичная проблема исходного запроса — это наложение условия ИЛИ в секции связи ПО (ON) левого соединения:

ПО ... И (Контрагенты.Наименование = Контрагенты1.Наименование ИЛИ Контрагенты.ИНН = Контрагенты1.ИНН ИЛИ ...)

Для СУБД (будь то Microsoft SQL Server или PostgreSQL) оператор ИЛИ в условиях соединения является крайне тяжелой операцией. База данных не может использовать стандартные индексы по отдельным полям (происходит так называемый Index Seek) и вынуждена переходить к полному сканированию всей таблицы (Table Scan) или к неоптимальным вложенным циклам (Nested Loops).

Когда СКД генерирует итоговый запрос, она оборачивает исходный текст во внешние выборки для вычисления итогов и наложения системных отборов. Под воздействием этих «надстроек» план выполнения СУБД ухудшается в геометрической прогрессии. Запрос, выполнявшийся в консоли 30 секунд, в недрах СКД либо уходит в бесконечный расчет (таймаут соединения), либо СУБД аварийно завершает поток выполнения из-за нехватки ресурсов TempDB, возвращая пустой результат.

Причина 3. Логическое противоречие ЛЕВОГО соединения и секции ГДЕ

Проанализируем следующую конструкцию в исходном запросе:

ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты1 ... ГДЕ НЕ Контрагенты1.ПометкаУдаления

Использование проверки полей правой таблицы левого соединения в секции ГДЕ — это классическая логическая ошибка. Если для контрагента из левой таблицы не найдется дубля в правой, то все поля таблицы Контрагенты1 примут значение NULL. Проверка НЕ NULL (или попытка сравнения NULL = ЛОЖЬ) в секции ГДЕ вернет значение Unknown (ложь). В результате СУБД отсечет эту строку.

Таким образом, левое соединение неявно превращается во внутреннее соединение (INNER JOIN). Такое расхождение логики также мешает оптимизатору СКД правильно построить итоговый запрос и может приводить к непредсказуемой фильтрации данных.

Решение проблемы: Рефакторинг через ОБЪЕДИНИТЬ ВСЕ

Чтобы гарантировать стабильный вывод результатов в СКД и ускорить работу отчета в сотни раз, необходимо отказаться от оператора ИЛИ в связях и разбить исходный запрос на три простых независимых запроса, объединив их результаты с помощью конструкции ОБЪЕДИНИТЬ ВСЕ.

Рассмотрим правильный оптимизированный вариант запроса для СКД:


ВЫБРАТЬ
    Контрагенты.Ссылка КАК Оригинал,
    Контрагенты1.Ссылка КАК Дубль,
    "Совпадает полное наименование" КАК ТипДублирования
ИЗ
    Справочник.Контрагенты КАК Контрагенты
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты1
        ПО Контрагенты.Ссылка <> Контрагенты1.Ссылка
            И Контрагенты.Код < Контрагенты1.Код
            И Контрагенты.Наименование = Контрагенты1.Наименование
ГДЕ
    НЕ Контрагенты.ПометкаУдаления
    И НЕ Контрагенты1.ПометкаУдаления

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Контрагенты.Ссылка КАК Оригинал,
    Контрагенты1.Ссылка КАК Дубль,
    "Совпадает короткое наименование" КАК ТипДублирования
ИЗ
    Справочник.Контрагенты КАК Контрагенты
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты1
        ПО Контрагенты.Ссылка <> Контрагенты1.Ссылка
            И Контрагенты.Код < Контрагенты1.Код
            И Контрагенты.КороткоеНаименование = Контрагенты1.КороткоеНаименование
ГДЕ
    НЕ Контрагенты.ПометкаУдаления
    И НЕ Контрагенты1.ПометкаУдаления

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Контрагенты.Ссылка КАК Оригинал,
    Контрагенты1.Ссылка КАК Дубль,
    "Совпадает ИНН" КАК ТипДублирования
ИЗ
    Справочник.Контрагенты КАК Контрагенты
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты1
        ПО Контрагенты.Ссылка <> Контрагенты1.Ссылка
            И Контрагенты.Код < Контрагенты1.Код
            И Контрагенты.ИНН = Контрагенты1.ИНН
ГДЕ
    НЕ Контрагенты.ПометкаУдаления
    И НЕ Контрагенты1.ПометкаУдаления

Почему этот вариант работает безотказно?

Проанализируем изменения и их влияние на поведение системы компоновки данных:

  1. Использование индексов: Теперь в каждом из трех блоков соединение происходит по конкретному точному равенству (по наименованию, короткому наименованию или ИНН). СУБД мгновенно находит соответствующие записи, используя имеющиеся индексы СУБД. Время выполнения падает с 30 секунд до долей секунды.
  2. Предсказуемость для СКД: Запросы с конструкцией ОБЪЕДИНИТЬ ВСЕ представляют собой линейную структуру. СКД гораздо легче транслирует их в итоговый SQL-код без искажения логики связей.
  3. Внутреннее соединение вместо левого: Поскольку нам нужны только реальные дубликаты (где правая часть гарантированно заполнена), мы заменили ресурсоемкое ЛЕВОЕ СОЕДИНЕНИЕ на эффективное ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN). Это исключило неопределенности со значениями NULL при наложении фильтров в секции ГДЕ.

Рекомендации по диагностике СКД на будущее

Если в процессе разработки вы снова столкнулись с пустым результатом в СКД при работающем запросе, выполните следующие шаги:

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