Когда мы сталкиваемся с необходимостью программного формирования отчетов с использованием Системы Компоновки Данных (СКД) в 1С:Предприятии 8.3, одной из частых проблем становится получение пустой ТаблицаЗначений в результате. Давайте разберем эту ситуацию подробно, выясним причины и найдем эффективные способы решения.
Прежде чем погрузиться в детали, давайте вспомним основные этапы программного использования СКД:
СхемаКомпоновкиДанных (обычно из макета).КомпоновщикНастроекКомпоновкиДанных и инициализируем его схемой.НастройкиКомпоновкиДанных (отборы, параметры, выбранные поля, структура).КомпоновщикМакетаКомпоновкиДанных и выполняем его, получая МакетКомпоновкиДанных.ПроцессорКомпоновкиДанных полученным макетом.ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений (или другой процессор вывода) для получения результата в ТаблицаЗначений.Центральную роль здесь играет объект КомпоновщикНастроекКомпоновкиДанных и его свойство Настройки. Именно в этом объекте собираются все параметры, которые будут влиять на окончательный вид и содержимое нашего отчета.
Давайте рассмотрим типовой фрагмент кода, который приводит к проблеме:
Если СтрокаНастройкиОтбора.Заполнено Тогда
КомпоновщикНастроек = Новый КомпоновщикНастроекКомпоновкиДанных;
ВнешнийМакетСКД = Справочники.НастройкиОбменовСМаркетплейсами.ПолучитьМакет(СтрокаНастройкиОтбора.ИмяОтбора);
КомпоновщикНастроек.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(ВнешнийМакетСКД));
НастройкиСКД = ОбменСМаркетплейсамиУТ11КлиентСервер.ДеСериализоватьНастройкиОтбора(СтрокаНастройкиОтбора.НастройкаОтбораXML);
КомпоновщикНастроек.ЗагрузитьНастройки(НастройкиСКД); // <--- Вероятное место возникновения проблемы
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетВывода = КомпоновщикМакета.Выполнить(ВнешнийМакетСКД, КомпоновщикНастроек.ПолучитьНастройки,,, Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетВывода);
РезультатОтбора = Новый ТаблицаЗначений;
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ПроцессорВывода.УстановитьОбъект(РезультатОтбора);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецЕсли;
Основная причина, по которой РезультатОтбора может оказаться пустой, кроется в строке КомпоновщикНастроек.ЗагрузитьНастройки(НастройкиСКД);. Разберем, что здесь происходит:
КомпоновщикНастроек.Инициализировать(...): На этом этапе КомпоновщикНастроек загружает все настройки по умолчанию из нашей СхемаКомпоновкиДанных (в данном случае, ВнешнийМакетСКД). Это включает в себя не только возможные стандартные отборы и параметры, но и обязательно ВыбранныеПоля (поля, которые мы хотим видеть в отчете) и Структура (как эти поля должны быть сгруппированы, детализированы и т.д.).НастройкиСКД = ...ДеСериализоватьНастройкиОтбора(...): Мы десериализуем настройки из XML-строки. Если этот XML содержит только информацию об отборах, а части, отвечающие за ВыбранныеПоля и Структура, в нем отсутствуют или пусты, то это приведет к проблеме.КомпоновщикНастроек.ЗагрузитьНастройки(НастройкиСКД);: Эта строка берет настройки из объекта НастройкиСКД и применяет их к КомпоновщикНастроек. При этом происходит перезапись соответствующих частей. Если в НастройкиСКД поля ВыбранныеПоля и Структура пусты, то они перезапишут те, что были установлены по умолчанию при инициализации.В результате, после загрузки настроек, КомпоновщикНастроек может оказаться с пустыми ВыбранныеПоля и/или пустой Структура. Если СКД не знает, какие поля выводить и в какой структуре, она просто не сможет сформировать результат, и наша ТаблицаЗначений останется пустой.
Для диагностики этой проблемы нам необходимо посмотреть, что находится в объекте Настройки компоновщика после загрузки. Разберем шаги:
КомпоновщикНастроек.ЗагрузитьНастройки(НастройкиСКД);.КомпоновщикНастроек: В окне отладчика найдите переменную КомпоновщикНастроек.Настройки: Разверните свойство Настройки объекта КомпоновщикНастроек.ВыбранныеПоля и Структура: Особое внимание уделите коллекциям ВыбранныеПоля и Структура. Если вы видите, что эти коллекции пусты (Количество() = 0), то это и есть корень проблемы. Для просмотра содержимого коллекций, особенно в старых версиях отладчика, может потребоваться нажать на кнопку "Показать значение в отдельном окне", как было отмечено в обсуждении.Этот шаг поможет нам подтвердить, что именно происходит с настройками после их загрузки.
Существует несколько подходов к решению этой проблемы, в зависимости от того, как мы планируем использовать сохраненные настройки.
Наиболее чистый подход — это убедиться, что XML, который мы десериализуем в НастройкиСКД, всегда содержит все необходимые части: отборы, параметры, а также ВыбранныеПоля и Структура. Если вы формируете этот XML из конструктора СКД или из пользовательских настроек, всегда сохраняйте их целиком. Когда пользователь редактирует только отборы, вы должны сохранять *все* текущие настройки компоновщика, а не только измененные отборы.
Это означает, что метод ОбменСМаркетплейсамиУТ11КлиентСервер.ДеСериализоватьНастройкиОтбора должен быть универсальным и десериализовывать полный объект настроек, а не только его часть, связанную с отборами. И, соответственно, метод, который сериализует настройки в XML (не показанный в примере), должен сериализовывать их целиком.
Если по какой-то причине мы не можем гарантировать полноту XML (например, если он создавался вручную или из внешнего источника, где хранились только отборы), мы можем программно дополнить ВыбранныеПоля и Структуру после загрузки отбора.
Рассмотрим, как это можно сделать:
Если СтрокаНастройкиОтбора.Заполнено Тогда
КомпоновщикНастроек = Новый КомпоновщикНастроекКомпоновкиДанных;
ВнешнийМакетСКД = Справочники.НастройкиОбменовСМаркетплейсами.ПолучитьМакет(СтрокаНастройкиОтбора.ИмяОтбора);
КомпоновщикНастроек.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(ВнешнийМакетСКД));
НастройкиСКД = ОбменСМаркетплейсамиУТ11КлиентСервер.ДеСериализоватьНастройкиОтбора(СтрокаНастройкиОтбора.НастройкаОтбораXML);
// Загружаем настройки, которые могут быть неполными
КомпоновщикНастроек.ЗагрузитьНастройки(НастройкиСКД);
// *************************************************************
// Дополнение настроек: Проверяем и заполняем ВыбранныеПоля
// *************************************************************
Если КомпоновщикНастроек.Настройки.ВыбранныеПоля.Количество() = 0 Тогда
// Пример заполнения: добавляем несколько полей, которые нам точно нужны
КомпоновщикНастроек.Настройки.ВыбранныеПоля.Добавить(Новый ВыбранноеПолеКомпоновкиДанных("Номенклатура"));
КомпоновщикНастроек.Настройки.ВыбранныеПоля.Добавить(Новый ВыбранноеПолеКомпоновкиДанных("Характеристика"));
КомпоновщикНастроек.Настройки.ВыбранныеПоля.Добавить(Новый ВыбранноеПолеКомпоновкиДанных("Количество"));
КомпоновщикНастроек.Настройки.ВыбранныеПоля.Добавить(Новый ВыбранноеПолеКомпоновкиДанных("Цена"));
// Или, если у нас есть "основной вариант" настроек, мы можем взять поля из него
// Например, если ВнешнийМакетСКД содержит "основной" вариант настроек
// КомпоновщикНастроек.Настройки.ВыбранныеПоля.ЗагрузитьНастройки(
// КомпоновщикНастроек.НастройкиПоУмолчанию.ВыбранныеПоля);
КонецЕсли;
// *************************************************************
// Дополнение настроек: Проверяем и заполняем Структуру вывода
// *************************************************************
Если КомпоновщикНастроек.Настройки.Структура.Количество() = 0 Тогда
// Добавляем простую структуру: детальные записи или группировку
ЭлементСтруктуры = КомпоновщикНастроек.Настройки.Структура.Добавить(Тип("ЭлементСтруктурыКомпоновкиДанныхТаблица")); // Или Тип("ЭлементСтруктурыКомпоновкиДанныхГруппировка")
ЭлементСтруктуры.Использование = Истина;
// Если это таблица или детальные записи, то просто добавляем выбранные поля
Для Каждого ВыбранноеПоле Из КомпоновщикНастроек.Настройки.ВыбранныеПоля Цикл
ЭлементСтруктуры.ВыбранныеПоля.Добавить(ВыбранноеПоле);
КонецЦикла;
// Если это группировка, то нужно указать поля группировки
// ЭлементГруппировки = ЭлементСтруктуры.Группировки.Добавить(Новый ПолеГруппировкиКомпоновкиДанных("Номенклатура"));
// ЭлементГруппировки.Использование = Истина;
КонецЕсли;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетВывода = КомпоновщикМакета.Выполнить(ВнешнийМакетСКД, КомпоновщикНастроек.ПолучитьНастройки,,, Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетВывода);
РезультатОтбора = Новый ТаблицаЗначений;
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ПроцессорВывода.УстановитьОбъект(РезультатОтбора);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецЕсли;
В этом примере мы сначала загружаем настройки (которые могут быть неполными), а затем проверяем ВыбранныеПоля и Структура. Если они пусты, мы программно добавляем минимально необходимый набор полей и базовую структуру вывода. Это гарантирует, что СКД будет иметь достаточно информации для формирования результата.
Обратите внимание на возможность использования КомпоновщикНастроек.НастройкиПоУмолчанию, чтобы взять стандартные поля или структуру из схемы, если это применимо.
В некоторых случаях, если мы хотим применить только отборы из внешнего источника, не затрагивая остальные настройки, вместо полного вызова ЗагрузитьНастройки() можно вручную очистить и заполнить коллекцию отборов. Этот подход более сложный, но дает полный контроль:
Если СтрокаНастройкиОтбора.Заполнено Тогда
КомпоновщикНастроек = Новый КомпоновщикНастроекКомпоновкиДанных;
ВнешнийМакетСКД = Справочники.НастройкиОбменовСМаркетплейсами.ПолучитьМакет(СтрокаНастройкиОтбора.ИмяОтбора);
КомпоновщикНастроек.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(ВнешнийМакетСКД));
НастройкиСКД = ОбменСМаркетплейсамиУТ11КлиентСервер.ДеСериализоватьНастройкиОтбора(СтрокаНастройкиОтбора.НастройкаОтбораXML);
// Очищаем текущие отборы (если они были в схеме по умолчанию)
КомпоновщикНастроек.Настройки.Отбор.Элементы.Очистить();
// Рекурсивно заполняем отборы из НастройкиСКД
Для Каждого ЭлементОтбора Из НастройкиСКД.Отбор.Элементы Цикл
КомпоновщикНастроек.Настройки.Отбор.Элементы.Добавить(ЭлементОтбора);
КонецЦикла;
// Остальные части настроек (ВыбранныеПоля, Структура) остаются теми,
// что были установлены при КомпоновщикНастроек.Инициализировать()
// и не перезаписываются.
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетВывода = КомпоновщикМакета.Выполнить(ВнешнийМакетСКД, КомпоновщикНастроек.ПолучитьНастройки,,, Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетВывода);
РезультатОтбора = Новый ТаблицаЗначений;
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ПроцессорВывода.УстановитьОбъект(РезультатОтбора);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецЕсли;
Этот подход позволяет нам применить только специфические изменения (например, пользовательские отборы), сохраняя при этом все остальные настройки по умолчанию из схемы компоновки данных.
При использовании ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений для вывода в ТаблицаЗначений важно помнить о некоторых ограничениях:
Убедитесь, что ваша структура вывода соответствует этим ограничениям, если вы стремитесь получить плоскую ТаблицаЗначений.
Иногда даже при правильно настроенных ВыбранныхПолях и Структуре результат может быть пустым, если сам запрос СКД не возвращает данные. Это может произойти из-за сложных отборов, отсутствия данных или неожиданной "оптимизации" запроса самой СКД. Для анализа итогового запроса мы можем использовать:
Проанализируйте итоговый запрос, который СКД формирует на основе ваших настроек. Выполните его напрямую к базе данных, чтобы убедиться, что он возвращает ожидаемые данные.
Если в вашем запросе СКД используются агрегатные функции (ресурсы) или поля, которые могут содержать NULL, это может привести к неожиданным результатам, вплоть до отсутствия строк. Например, если вы считаете сумму по полю, которое часто бывает NULL, и не обрабатываете это. Рекомендуется использовать функцию ЕСТЬNULL() в запросе для корректной обработки таких значений, приводя их к желаемому виду (например, к 0 для числовых полей):
ВЫБРАТЬ
Номенклатура.Наименование КАК Номенклатура,
ЕСТЬNULL(ТаблицаОстатков.Количество, 0) КАК КоличествоНаСкладе,
ЕСТЬNULL(ТаблицаЦен.Цена, 0) КАК Цена
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваров.Остатки КАК ТаблицаОстатков
ПО Номенклатура.Ссылка = ТаблицаОстатков.Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК ТаблицаЦен
ПО Номенклатура.Ссылка = ТаблицаЦен.Номенклатура
Такой подход помогает гарантировать, что даже при отсутствии данных по связанным таблицам, основные строки отчета будут присутствовать с нулевыми или другими заданными значениями.
После выполнения КомпоновщикМакета.Выполнить(), мы получаем объект МакетВывода. Этот объект представляет собой скомпонованный макет отчета, который будет обрабатывать процессор компоновки. В отладчике можно просмотреть содержимое этого макета, чтобы убедиться, что он содержит ожидаемые области вывода, поля и структуру. Если МакетВывода выглядит пустым или некорректным, это может указывать на проблему с предыдущими этапами формирования настроек.
Проблема пустой ТаблицаЗначений при программном использовании СКД чаще всего сводится к неполным настройкам компоновки данных, а именно к отсутствию или перезаписи ВыбранныхПолей и Структуры. Тщательная отладка объекта КомпоновщикНастроек.Настройки, а также при необходимости программное дополнение или слияние настроек, помогут вам эффективно решить эту задачу. Всегда помните о полном жизненном цикле СКД и о том, как каждая часть настроек влияет на конечный результат.