Почему СКД возвращает пустые значения (NULL) при левом соединении, хотя в консоли запросов всё работает правильно?

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

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

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


Проанализируем ситуацию: в чем суть проблемы?

Рассмотрим практический пример. Нам необходимо построить простой отчет по проверке наличия пробитых чеков по эквайринговым операциям. В новых релизах конфигураций «Управление торговлей» появился регистр сведений ФискальныеОперации, который хранит информацию о пробитых чеках, ссылаясь на документ-основание через поле ДокументОснование. Мы пишем запрос, в котором выбираем данные из этого регистра во временную таблицу, а затем левым соединением связываем документы ОперацияПоПлатежнойКарте с этой временной таблицей.

Пример исходного запроса:


ВЫБРАТЬ
    ФискальныеОперации.ДокументОснование КАК ДокументОснование,
    ФискальныеОперации.ИдентификаторЗаписи КАК ИдентификаторЗаписи,
    ФискальныеОперации.Дата КАК ДатаЭквайринга
ПОМЕСТИТЬ ВТЖФС
ИЗ
    РегистрСведений.ФискальныеОперации КАК ФискальныеОперации
ГДЕ
    ФискальныеОперации.Дата МЕЖДУ &Дата1 И &Дата2
;
 ////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ОперацияПоПлатежнойКарте.Ссылка КАК Эквайринг,
    ВТЖФС.ИдентификаторЗаписи КАК ИдентификаторЗаписиЭквайринга,
    ВТЖФС.ДатаЭквайринга КАК ДатаПоЖурналуЭквайринг,
    НАЧАЛОПЕРИОДА(ВТЖФС.ДатаЭквайринга, ДЕНЬ) = НАЧАЛОПЕРИОДА(ОперацияПоПлатежнойКарте.Ссылка.Дата, ДЕНЬ) КАК СовпадаютДатыЭквайринг,
    ОперацияПоПлатежнойКарте.СуммаДокумента КАК СуммаЭквайринга
ИЗ
    Документ.ОперацияПоПлатежнойКарте КАК ОперацияПоПлатежнойКарте
        ЛЕВОЕ СОЕДИНЕНИЕ ВТЖФС КАК ВТЖФС
        ПО ОперацияПоПлатежнойКарте.Ссылка = ВТЖФС.ДокументОснование
ГДЕ
    ОперацияПоПлатежнойКарте.Дата МЕЖДУ &Дата1 И &Дата2
    И ОперацияПоПлатежнойКарте.ЭквайринговыйТерминал В(&ЭквайринговыйТерминал)
    И ОперацияПоПлатежнойКарте.Проведен
УПОРЯДОЧИТЬ ПО
    ОперацияПоПлатежнойКарте.Дата

Когда мы запускаем этот запрос в консоли, СУБД возвращает нам все связанные строки: мы видим документы эквайринга и соответствующие им фискальные чеки. Однако при формировании отчета на СКД колонки ИдентификаторЗаписиЭквайринга и ДатаПоЖурналуЭквайринг остаются абсолютно пустыми (заполненными значениями NULL). Давайте выясним причины такого поведения.


Выясним причины: почему СКД «теряет» данные?

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

  1. Составной тип данных связующего поля. Поле ДокументОснование в регистре ФискальныеОперации имеет составной тип данных (оно может ссылаться на десятки различных видов документов: реализации, чеки ККМ, операции по картам и т.д.). Когда мы выполняем левое соединение ПО ОперацияПоПлатежнойКарте.Ссылка = ВТЖФС.ДокументОснование, СКД пытается оптимизировать разворачивание этого составного типа на уровне SQL-запроса. Если в настройках компоновщика явно не указано, с каким конкретно типом происходит связь, оптимизатор платформы может некорректно построить структуру дерева запроса, из-за чего СУБД вернет пустой результат для правой таблицы.
  2. Механизм удаления неиспользуемых полей (оптимизация СКД). Если СКД «считает», что какое-то поле из правой таблицы (ВТЖФС) напрямую не выводится в группировках отчета или не задействовано в итоговом макете, компоновщик может полностью исключить это поле или даже саму таблицу из результирующего запроса к СУБД, либо заменить его на NULL, чтобы снизить нагрузку на сервер базы данных.
  3. Влияние ограничений RLS (права доступа на уровне записей). Даже если у текущего пользователя установлены «Полные права», при формировании отчета в режиме предприятия могут неявно применяться ограничения прав доступа (RLS) или функциональные опции, которые ограничивают доступ к некоторым типам документов из составного типа поля ДокументОснование. В консоли запросов этот запрос может выполняться в привилегированном режиме, обходя эти ограничения, а в реальном отчете СКД — с учетом всех ограничений прав, что приводит к «отсечению» записей.

Разберем по шагам методы решения проблемы

Для исправления этого поведения существует несколько надежных способов. Рекомендуется использовать их в комплексе.

Способ 1. Явное приведение типов через функцию ВЫРАЗИТЬ (Рекомендуемый)

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

Давайте изменим первый пакет нашего запроса следующим образом:


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

Что это нам дает? Конструкция ВЫРАЗИТЬ(... КАК ...) приводит поле к строго определенному простому ссылочному типу. Дополнительное условие ССЫЛКА отсекает из выборки все остальные типы документов на самом раннем этапе. В результате СУБД выполняет соединение по индексу простого типа, исключая сложные многотабличные соединения SQL, характерные для составных типов данных. СКД больше не будет путаться при генерации макета компоновки.

Способ 2. Установка флага «Обязательное» для связующих полей в СКД

Чтобы компоновщик макета СКД гарантированно не выбрасывал связующие поля из результирующего SQL-запроса, нам нужно зафиксировать их поведение в схеме компоновки данных.

Выполним следующие шаги:

  1. Откроем схему компоновки данных нашего отчета.
  2. Перейдем на вкладку «Наборы данных».
  3. Найдем в списке полей поле ДокументОснование (из временной таблицы ВТЖФС) или поле связи.
  4. В колонке «Роли» нажмем на кнопку редактирования.
  5. В открывшемся окне установим флаг «Обязательное».

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

Способ 3. Отключение автоматического заполнения полей и использование фигурных скобок

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

Проанализируем альтернативный вариант настройки:

  1. На вкладке «Наборы данных» снимем флаг «Автозаполнение» для нашего запроса.
  2. Вручную добавим только те поля, которые действительно должны быть доступны пользователю для вывода и настройки отборов.
  3. В тексте запроса используем фигурные скобки {} для явного указания мест, куда СКД имеет право помещать пользовательские фильтры.

Например, секцию ограничений в запросе можно описать так:


ГДЕ
    ОперацияПоПлатежнойКарте.Дата МЕЖДУ &Дата1 И &Дата2
    И ОперацияПоПлатежнойКарте.Проведен
    И Отрасль {ОперацияПоПлатежнойКарте.ЭквайринговыйТерминал.* КАК ЭквайринговыйТерминал}

Это полностью заблокирует попытки СКД самовольно модифицировать условия соединения во временных таблицах.

Способ 4. Выполнение отчета в привилегированном режиме

Если данные все равно не выводятся из-за наложения ограничений RLS у конкретного пользователя, мы можем принудительно запустить компоновку результата в привилегированном режиме, временно отключив проверку прав на уровне записей.

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


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

Этот способ гарантирует, что отчет соберет абсолютно все существующие в базе данных записи, так же как это делает разработчик под полными правами в консоли запросов.


Как гарантированно поймать ошибку: диагностика через Инструменты Разработчика

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

Выполним следующие шаги для анализа реального запроса:

  1. Воспользуемся внешней обработкой «Инструменты разработчика» (автор tormozit) или специализированной консолью компоновки данных.
  2. Запустим отчет в режиме отладки и установим точку останова в модуле объекта отчета в процедуре ПриКомпоновкеРезультата на моменте получения макета компоновки:
    МакетКомпоновки = КомпоновщикМакета.Выполнить(...);
  3. Вычислим выражение МакетКомпоновки.НаборыДанных.НаборДанных1.Запрос (имя набора данных может отличаться).
  4. Скопируем полученный сгенерированный текст запроса и вставим его в обычную консоль запросов.

В 99% случаев вы увидите, что сгенерированный СКД запрос сильно отличается от вашего исходного: там могут отсутствовать связи, добавлены лишние фильтры или левое соединение преобразовано во внутреннее. Анализ этого «реального» запроса сразу укажет вам, какое именно поле или таблица подверглись деструктивной оптимизации компоновщика, после чего вы сможете точечно применить к ним приведение типов ВЫРАЗИТЬ или установить флаг «Обязательное».

← На главную