Почему СКД удаляет поле-выражение из запроса и как это исправить?

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

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

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

Выясняем причину: почему оптимизатор СКД "съедает" выражения

Механизм оптимизации СКД работает по принципу построения дерева зависимостей полей "снизу вверх" — от финальных настроек, выбранных пользователем в схеме компоновки, до исходных таблиц базы данных. Если на каком-то этапе поле передается напрямую (например, Выборка.Поле КАК Поле), СКД без труда сопоставляет псевдонимы и отслеживает путь поля.

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

Проанализируем классический пример проблемного запроса, где поле ГлючноеПоле пропадает после компоновки:


ВЫБРАТЬ
    ГлючноеПоле,
    НеглючноеПоле,
    НенужноеПоле1,
    НенужноеПоле2
ПОМЕСТИТЬ ВТ
ИЗ
    (ВЫБРАТЬ
        "г" КАК ГлючноеПоле,
        "нг" КАК НеглючноеПоле,
        "нн1" КАК НенужноеПоле1,
        "нн2" КАК НенужноеПоле2
    {ВЫБРАТЬ
        ГлючноеПоле,
        НеглючноеПоле,
        НенужноеПоле1,
        НенужноеПоле2}
    ) КАК ВложенныйЗапрос;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ГлючноеПоле,
    НеглючноеПоле,
    НенужноеПоле1,
    НенужноеПоле2
{ВЫБРАТЬ
    ГлючноеПоле,
    НеглючноеПоле,
    НенужноеПоле1,
    НенужноеПоле2}
ИЗ
    (ВЫБРАТЬ
        ГлючноеПоле + "выражение" КАК ГлючноеПоле,
        НеглючноеПоле,
        НенужноеПоле1,
        НенужноеПоле2
    ИЗ
        (ВЫБРАТЬ
            ВыборкаИзВТ.ГлючноеПоле,
            ВыборкаИзВТ.НеглючноеПоле,
            ПрисоединеннаяВТ.НенужноеПоле1,
            ПрисоединеннаяВТ.НенужноеПоле2
        ИЗ
            (ВЫБРАТЬ
                ВТ.ГлючноеПоле,
                ВТ.НеглючноеПоле
            ИЗ
                ВТ КАК ВТ
            ) КАК ВыборкаИзВТ
            ЛЕВОЕ СОЕДИНЕНИЕ ВТ КАК ПрисоединеннаяВТ
            ПО ВыборкаИзВТ.ГлючноеПоле = ПрисоединеннаяВТ.ГлючноеПоле
        ) КАК СоединениеС_ВТ
    ) КАК ЗдесьПолеИсчезает

Если пользователь выберет в настройках только ГлючноеПоле и НеглючноеПоле, оптимизатор попытается вырезать неиспользуемые поля. При этом из-за наличия операции конкатенации строк во вложенном запросе ЗдесьПолеИсчезает, компоновщик полностью исключит расчет поля ГлючноеПоле.

Рассмотрим по шагам основные и наиболее эффективные варианты решения этой проблемы.

Способ 1. Использование временных таблиц вместо вложенных запросов

Это наиболее надежный архитектурный подход, который полностью решает проблему оптимизации и одновременно повышает производительность отчета на уровне СУБД. Процессор компоновки данных гораздо корректнее и предсказуемее работает с последовательными временными таблицами, нежели с многократно вложенными подзапросами в секции ИЗ (FROM).

Для исправления ситуации перепишем запрос, поместив результат соединения во временную таблицу СоединениеС_ВТ непосредственно перед вычислением выражения. Разберем этот пример:


ВЫБРАТЬ
    ГлючноеПоле,
    НеглючноеПоле,
    НенужноеПоле1,
    НенужноеПоле2
ПОМЕСТИТЬ ВТ
ИЗ
    (ВЫБРАТЬ
        "г" КАК ГлючноеПоле,
        "нг" КАК НеглючноеПоле,
        "нн1" КАК НенужноеПоле1,
        "нн2" КАК НенужноеПоле2
    {ВЫБРАТЬ
        ГлючноеПоле,
        НеглючноеПоле,
        НенужноеПоле1,
        НенужноеПоле2}
    ) КАК ВложенныйЗапрос;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВыборкаИзВТ.ГлючноеПоле,
    ВыборкаИзВТ.НеглючноеПоле,
    ПрисоединеннаяВТ.НенужноеПоле1,
    ПрисоединеннаяВТ.НенужноеПоле2
ПОМЕСТИТЬ СоединениеС_ВТ
ИЗ
    (ВЫБРАТЬ
        ВТ.ГлючноеПоле,
        ВТ.НеглючноеПоле
    ИЗ
        ВТ КАК ВТ
    ) КАК ВыборкаИзВТ
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ КАК ПрисоединеннаяВТ
    ПО ВыборкаИзВТ.ГлючноеПоле = ПрисоединеннаяВТ.ГлючноеПоле;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ГлючноеПоле,
    НеглючноеПоле,
    НенужноеПоле1,
    НенужноеПоле2
{ВЫБРАТЬ
    ГлючноеПоле,
    НеглючноеПоле,
    НенужноеПоле1,
    НенужноеПоле2}
ИЗ
    (ВЫБРАТЬ
        СоединениеС_ВТ.ГлючноеПоле + "выражение" КАК ГлючноеПоле,
        СоединениеС_ВТ.НеглючноеПоле,
        СоединениеС_ВТ.НенужноеПоле1,
        СоединениеС_ВТ.НенужноеПоле2
    ИЗ
        СоединениеС_ВТ КАК СоединениеС_ВТ
    ) КАК ЗдесьПолеИсчезает

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

Способ 2. Явное управление полями через фигурные скобки во вложенных запросах

Если по каким-то причинам плодить временные таблицы не хочется, мы можем вручную подсказать компоновщику, как управлять составом полей во вложенных подзапросах. Для этого используются специальные расширения языка запросов СКД — фигурные скобки {ВЫБРАТЬ...}.

Важное правило: фигурные скобки для выбора полей должны быть объявлены на том уровне вложенного запроса, где эти поля впервые извлекаются или формируются. Проанализируем пример настройки:


ВЫБРАТЬ
    ВТ1.А ПОМЕСТИТЬ ВТ1
ИЗ
    (ВЫБРАТЬ
        "А" КАК А
    {ВЫБРАТЬ
        А}
    ) КАК ВТ1;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ2.Б,
    ВТ2.Х
ПОМЕСТИТЬ ВТ2
ИЗ
    (ВЫБРАТЬ
        "Б" КАК Б,
        "Х" КАК Х
    {ВЫБРАТЬ
        Б,
        Х}
    ) КАК ВТ2;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ1.А,
    ВТ2.Б,
    ВТ2.Х
{ВЫБРАТЬ
    А,
    Б,
    Х}
ИЗ
    ВТ1 КАК ВТ1
    {ЛЕВОЕ СОЕДИНЕНИЕ ВТ2 КАК ВТ2
    ПО ВТ1.А = ВТ2.Х}

Если пользователь выбирает только поля А и Б, благодаря фигурной скобке компоновщик корректно исключит поле Х и само левое соединение из итоговой схемы, не ломая при этом логику запроса. Это позволяет динамически сокращать нагрузку на сервер СУБД, убирая неиспользуемые связи.

Способ 3. Использование роли поля «Обязательное» (Mandatory) в СКД

Существует элегантный способ запретить оптимизатору СКД исключать поле из запроса на любом из этапов компоновки без изменения исходного SQL-кода. Этот вариант реализуется через визуальные настройки схемы компоновки данных.

Разберем пошаговый алгоритм настройки:

  1. Откройте схему компоновки данных (файл .xsd) в конфигураторе или в консоли отчетов.
  2. Перейдите на вкладку «Наборы данных».
  3. В списке полей найдите исходное поле, на основании которого строится ваше проблемное выражение (например, ГлючноеПоле).
  4. Найдите колонку «Роль» и нажмите на кнопку выбора (три точки) для открытия окна редактирования роли поля.
  5. Установите флаг «Обязательное» (в англоязычной версии платформы — Mandatory).
  6. Сохраните настройки.

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

Способ 4. Перенос вычислений на уровень вычисляемых полей СКД

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

Проанализируем этот процесс:

  1. В самом запросе СКД выберите поле в "чистом" исходном виде, без применения формул (например, просто Выборка.ГлючноеПоле КАК ГлючноеПоле).
  2. Перейдите на вкладку «Вычисляемые поля» в конструкторе схемы компоновки данных.
  3. Добавьте новую строку, укажите путь к данным (например, ГлючноеПолеСВыражением).
  4. В колонке «Выражение» напишите нужную формулу на языке выражений СКД. Например: ГлючноеПоле + "выражение" или Выбор Когда НеглючноеПоле = "нг" Тогда "Значение1" Иначе "Значение2" Конец.
  5. Добавьте новое вычисляемое поле в список выбранных полей в настройках варианта отчета.

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

Способ 5. Использование фиктивного условия (Fake Condition) в запросе

Если вам крайне важно оставить вычисления внутри тела запроса, но при этом нет возможности использовать свойство «Обязательное» (например, при динамическом формировании текста запроса в коде), можно использовать небольшую хитрость — применить фиктивное условие в секции ГДЕ.

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


ГДЕ
    ГлючноеПоле <> "ЗаведомоНесуществующееЗначениеУникальное"
    ИЛИ ГлючноеПоле ЕСТЬ НЕ NULL

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

Анализ стандартов разработки 1С и производительности

Согласно стандартам разработки фирмы 1С, опубликованным на ИТС, использование вложенных запросов в секции ИЗ (особенно левых соединений с вложенными запросами) крайне не рекомендуется. СУБД не всегда способны построить эффективный план выполнения таких запросов из-за отсутствия статистик по виртуальным таблицам вложенных выборок.

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

← На главную