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

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

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

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

Причина 1: Динамическая оптимизация и «выбрасывание» таблиц оптимизатором СКД

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

Рассмотрим классическую ситуацию: у вас есть левое соединение основной таблицы продаж с таблицей курсов валют для расчета суммы в национальной валюте. В тексте запроса вы прописали операцию умножения: Цена * Курс.

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

В результате СУБД возвращает значение NULL для исключенного поля. Как известно, любая математическая операция с NULL (например, Цена * NULL) всегда дает в результате NULL. Из-за этого в СКД пропадают расчетные показатели, тогда как в консоли запросов (которая всегда выполняет запрос целиком и «честно») всё отображается корректно.

Решение проблемы с помощью функции ЕСТЬNULL и обязательных полей

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

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

Посмотрим на пример правильного написания выражения в запросе:


ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    ЕСТЬNULL(Продажи.Цена, 0) * ЕСТЬNULL(КурсыВалют.Курс, 1) КАК СуммаВВалюте
ИЗ
    РегистрНакопления.Продажи.Обороты КАК Продажи
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют.СрезПоследних КАК КурсыВалют
        ПО Продажи.Договор.ВалютаВзаиморасчетов = КурсыВалют.Валюта

Причина 2: Превращение ЛЕВОГО соединения во ВНУТРЕННЕЕ при наложении отборов

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

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

Причина 3: Ограничения прав доступа на уровне записей (RLS)

Разработчики практически всегда тестируют запросы в консоли под учетной записью с полными правами администратора. В этом режиме механизмы ограничения доступа (RLS) полностью отключены.

Когда же пользователь запускает этот отчет в режиме предприятия, СКД на лету внедряет в SQL-запрос сложные подзапросы ограничений прав доступа на уровне записей. Если у пользователя нет прав на чтение какого-то конкретного склада, организации или контрагента, СКД просто «отрежет» эти строки или заменит их значения на NULL, из-за чего итоговые суммы в отчете существенно уменьшатся по сравнению с консолью запросов.

Причина 4: Влияние функциональных опций

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

Причина 5: Кэширование данных и старые версии отчетов

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

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

Как увидеть реальный SQL-запрос, который выполняет СКД?

Чтобы раз и навсегда перестать гадать, почему расходятся данные, нужно проанализировать фактический текст запроса, сформированный компоновщиком макета.

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


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    
    // Отключаем стандартный вывод отчета системой
    СтандартнаяОбработка = Ложь;
    
    // Создаем компоновщик макета
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    
    // Генерируем макет компоновки с учетом текущих настроек пользователя
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки(), ДанныеРасшифровки);
    
    // Получаем реальный текст запроса, который будет отправлен в СУБД
    РеальныйТекстЗапроса = МакетКомпоновки.НаборыДанных.НаборДанных1.Запрос;
    
    // Устанавливаем точку останова на строке ниже и копируем значение РеальныйТекстЗапроса в буфер
    Прервать; 
    
    // Далее выводим отчет вручную через процессор компоновки
    ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
    
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
    ПроцессорВывода.Вывести(ПроцессорКомпоновки);
    
КонецПроцедуры

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

← На главную