Разработчики на платформе 1С:Предприятие 8 часто сталкиваются с загадочным поведением Системы компоновки данных (СКД): один и тот же запрос, запущенный в обычной консоли запросов или даже в консоли СКД, возвращает корректные заполненные данные, но при запуске реального отчета в режиме «1С:Предприятие» вместо ожидаемых значений мы видим пустые поля (NULL). Особенно часто эта проблема проявляется после обновления типовых конфигураций (например, «Управление торговлей 11», «ERP» или «Комплексная автоматизация»), когда разработчики начинают связывать новые регистры со старыми документами.
В этой статье мы подробно разберем, почему СКД ведет себя подобным образом, как работает механизм оптимизации запросов компоновщика «под капотом» и какими способами можно гарантировать стопроцентное совпадение результатов в консоли и в итоговом отчете.
Рассмотрим практический пример. Нам необходимо построить простой отчет по проверке наличия пробитых чеков по эквайринговым операциям. В новых релизах конфигураций «Управление торговлей» появился регистр сведений ФискальныеОперации, который хранит информацию о пробитых чеках, ссылаясь на документ-основание через поле ДокументОснование. Мы пишем запрос, в котором выбираем данные из этого регистра во временную таблицу, а затем левым соединением связываем документы ОперацияПоПлатежнойКарте с этой временной таблицей.
Пример исходного запроса:
ВЫБРАТЬ
ФискальныеОперации.ДокументОснование КАК ДокументОснование,
ФискальныеОперации.ИдентификаторЗаписи КАК ИдентификаторЗаписи,
ФискальныеОперации.Дата КАК ДатаЭквайринга
ПОМЕСТИТЬ ВТЖФС
ИЗ
РегистрСведений.ФискальныеОперации КАК ФискальныеОперации
ГДЕ
ФискальныеОперации.Дата МЕЖДУ &Дата1 И &Дата2
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ОперацияПоПлатежнойКарте.Ссылка КАК Эквайринг,
ВТЖФС.ИдентификаторЗаписи КАК ИдентификаторЗаписиЭквайринга,
ВТЖФС.ДатаЭквайринга КАК ДатаПоЖурналуЭквайринг,
НАЧАЛОПЕРИОДА(ВТЖФС.ДатаЭквайринга, ДЕНЬ) = НАЧАЛОПЕРИОДА(ОперацияПоПлатежнойКарте.Ссылка.Дата, ДЕНЬ) КАК СовпадаютДатыЭквайринг,
ОперацияПоПлатежнойКарте.СуммаДокумента КАК СуммаЭквайринга
ИЗ
Документ.ОперацияПоПлатежнойКарте КАК ОперацияПоПлатежнойКарте
ЛЕВОЕ СОЕДИНЕНИЕ ВТЖФС КАК ВТЖФС
ПО ОперацияПоПлатежнойКарте.Ссылка = ВТЖФС.ДокументОснование
ГДЕ
ОперацияПоПлатежнойКарте.Дата МЕЖДУ &Дата1 И &Дата2
И ОперацияПоПлатежнойКарте.ЭквайринговыйТерминал В(&ЭквайринговыйТерминал)
И ОперацияПоПлатежнойКарте.Проведен
УПОРЯДОЧИТЬ ПО
ОперацияПоПлатежнойКарте.Дата
Когда мы запускаем этот запрос в консоли, СУБД возвращает нам все связанные строки: мы видим документы эквайринга и соответствующие им фискальные чеки. Однако при формировании отчета на СКД колонки ИдентификаторЗаписиЭквайринга и ДатаПоЖурналуЭквайринг остаются абсолютно пустыми (заполненными значениями NULL). Давайте выясним причины такого поведения.
Главное отличие СКД от обычного запроса заключается в том, что СКД не выполняет исходный текст запроса «как есть». Компоновщик макета анализирует настройки отчета (выбранные поля, группировки, отборы) и на их основе динамически перестраивает (оптимизирует) запрос перед отправкой СУБД. Существует три основные технические причины, по которым эта оптимизация приводит к потере данных:
ДокументОснование в регистре ФискальныеОперации имеет составной тип данных (оно может ссылаться на десятки различных видов документов: реализации, чеки ККМ, операции по картам и т.д.). Когда мы выполняем левое соединение ПО ОперацияПоПлатежнойКарте.Ссылка = ВТЖФС.ДокументОснование, СКД пытается оптимизировать разворачивание этого составного типа на уровне SQL-запроса. Если в настройках компоновщика явно не указано, с каким конкретно типом происходит связь, оптимизатор платформы может некорректно построить структуру дерева запроса, из-за чего СУБД вернет пустой результат для правой таблицы.
ВТЖФС) напрямую не выводится в группировках отчета или не задействовано в итоговом макете, компоновщик может полностью исключить это поле или даже саму таблицу из результирующего запроса к СУБД, либо заменить его на NULL, чтобы снизить нагрузку на сервер базы данных.
ДокументОснование. В консоли запросов этот запрос может выполняться в привилегированном режиме, обходя эти ограничения, а в реальном отчете СКД — с учетом всех ограничений прав, что приводит к «отсечению» записей.
Для исправления этого поведения существует несколько надежных способов. Рекомендуется использовать их в комплексе.
ВЫРАЗИТЬ (Рекомендуемый)Поскольку в нашем отчете мы жестко связываем регистр с конкретным типом документа (Документ.ОперацияПоПлатежнойКарте), нам обязательно нужно сообщить об этом оптимизатору СКД и СУБД. Для этого мы принудительно типизируем поле составного типа во временной таблице.
Давайте изменим первый пакет нашего запроса следующим образом:
ВЫБРАТЬ
ВЫРАЗИТЬ(ФискальныеОперации.ДокументОснование КАК Документ.ОперацияПоПлатежнойКарте) КАК ДокументОснование,
ФискальныеОперации.ИдентификаторЗаписи КАК ИдентификаторЗаписи,
ФискальныеОперации.Дата КАК ДатаЭквайринга
ПОМЕСТИТЬ ВТЖФС
ИЗ
РегистрСведений.ФискальныеОперации КАК ФискальныеОперации
ГДЕ
ФискальныеОперации.Дата МЕЖДУ &Дата1 И &Дата2
И ФискальныеОперации.ДокументОснование ССЫЛКА Документ.ОперацияПоПлатежнойКарте
Что это нам дает? Конструкция ВЫРАЗИТЬ(... КАК ...) приводит поле к строго определенному простому ссылочному типу. Дополнительное условие ССЫЛКА отсекает из выборки все остальные типы документов на самом раннем этапе. В результате СУБД выполняет соединение по индексу простого типа, исключая сложные многотабличные соединения SQL, характерные для составных типов данных. СКД больше не будет путаться при генерации макета компоновки.
Чтобы компоновщик макета СКД гарантированно не выбрасывал связующие поля из результирующего SQL-запроса, нам нужно зафиксировать их поведение в схеме компоновки данных.
Выполним следующие шаги:
ДокументОснование (из временной таблицы ВТЖФС) или поле связи.Установка этого флага жестко указывает системе компоновки данных: «Это поле должно всегда присутствовать в результирующем запросе к базе данных, независимо от настроек группировок, выбранных полей и отборов пользователя». Это предотвратит оптимизацию, ломающую логику левого соединения.
По умолчанию для наборов данных в СКД включено свойство «Автозаполнение». Оно позволяет СКД самостоятельно анализировать текст запроса и автоматически определять доступные поля, параметры и отборы. Однако при работе со сложными пакетами запросов и временными таблицами автозаполнение может сыграть злую шутку, наложив неявные отборы непосредственно внутрь временной таблицы.
Проанализируем альтернативный вариант настройки:
{} для явного указания мест, куда СКД имеет право помещать пользовательские фильтры.Например, секцию ограничений в запросе можно описать так:
ГДЕ
ОперацияПоПлатежнойКарте.Дата МЕЖДУ &Дата1 И &Дата2
И ОперацияПоПлатежнойКарте.Проведен
И Отрасль {ОперацияПоПлатежнойКарте.ЭквайринговыйТерминал.* КАК ЭквайринговыйТерминал}
Это полностью заблокирует попытки СКД самовольно модифицировать условия соединения во временных таблицах.
Если данные все равно не выводятся из-за наложения ограничений RLS у конкретного пользователя, мы можем принудительно запустить компоновку результата в привилегированном режиме, временно отключив проверку прав на уровне записей.
Для этого откроем модуль объекта отчета и переопределим обработчик события ПриКомпоновкеРезультата:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// Переходим в привилегированный режим для чтения данных без RLS
УстановитьПривилегированныйРежим(Истина);
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки(), ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
УстановитьПривилегированныйРежим(Ложь);
КонецПроцедуры
Этот способ гарантирует, что отчет соберет абсолютно все существующие в базе данных записи, так же как это делает разработчик под полными правами в консоли запросов.
Рассмотрим, как быстро локализовать проблему, если описанные методы не помогли сразу. Самый надежный инструмент диагностики — это просмотр реального SQL-запроса, который СКД генерирует на лету.
Выполним следующие шаги для анализа реального запроса:
ПриКомпоновкеРезультата на моменте получения макета компоновки: МакетКомпоновки = КомпоновщикМакета.Выполнить(...);МакетКомпоновки.НаборыДанных.НаборДанных1.Запрос (имя набора данных может отличаться).В 99% случаев вы увидите, что сгенерированный СКД запрос сильно отличается от вашего исходного: там могут отсутствовать связи, добавлены лишние фильтры или левое соединение преобразовано во внутреннее. Анализ этого «реального» запроса сразу укажет вам, какое именно поле или таблица подверглись деструктивной оптимизации компоновщика, после чего вы сможете точечно применить к ним приведение типов ВЫРАЗИТЬ или установить флаг «Обязательное».