Как организовать вывод кастомной иерархии в табличной части документа при помощи СКД

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

При разработке отчетов в системе 1С:Предприятие часто возникает задача вывести данные табличной части документа в виде иерархической структуры. Ситуация усложняется, если иерархия строится не по стандартному признаку справочника (папка-элемент), а по кастомному реквизиту (например, связь типа «родитель-потомок» между элементами через реквизит ГлавныйСчетчик или текстовый код иерархии вида 1, 1.1, 1.2). В этой статье мы подробно разберем, как правильно спроектировать запрос, настроить Систему компоновки данных (СКД) и избежать классических ошибок группировки вложенных таблиц.

Анализ исходной проблемы и ошибки запроса

Рассмотрим ситуацию: у нас есть документ, в табличной части которого хранятся сведения о приборах учета (счетчиках), объемах потребления и стоимости. При этом в справочнике Счетчики настроена собственная иерархия элементов: каждый прибор учета может иметь ссылку на родительский (ГлавныйСчетчик). Нам необходимо вывести строки табличной части в отчет в строгом иерархическом порядке подчинения счетчиков.

Попытка написать прямой запрос к документу с выборкой вложенной табличной части и последующим упорядочиванием по иерархии часто приводит к системной ошибке:

{(5, 2)}: Группировка по таблицам верхнего уровня и вложенным таблицам

Выясним причину этой ошибки. При обращении к таблице документа Документ.РасчетСтоимостиВоды и получении его табличной части как вложенной таблицы (через точку РасчетСтоимостиВоды.ТЧРасчетСтоимостиПоСчетчикам.(...)) платформа пытается построить сложный иерархический SQL-запрос. Использование конструкции УПОРЯДОЧИТЬ ПО ... ИЕРАРХИЯ совместно с обращением к полям шапки документа и полям вложенной таблицы в одном запросе синтаксически недопустимо для СУБД. Платформа 1С не может корректно сопоставить строки верхнего уровня (документы) со строками вложенных таблиц при таком обращении.

Шаг 1. Перепроектирование запроса в СКД

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

Рассмотрим правильный вариант SQL-подобного запроса для СКД:


ВЫБРАТЬ
    ТЧ.Ссылка.ОтчетныйМесяц КАК ОтчетныйМесяц,
    ТЧ.Ссылка.ОтчетныйГод КАК ОтчетныйГод,
    ТЧ.Ссылка.Организация КАК Организация,
    ТЧ.Контрагенты КАК Контрагент,
    ТЧ.Помещения КАК МестоУстановки,
    ТЧ.Счетчик КАК Счетчик,
    ТЧ.Счетчик.ГлавныйСчетчик КАК ГлавныйСчетчик,
    ТЧ.НачалоМесяца КАК НачалоМесяца,
    ТЧ.КонецМесяца КАК КонецМесяца,
    ТЧ.Итого КАК Итого,
    ТЧ.СтоимостьУслуг КАК СтоимостьУслуг,
    ТЧ.СуммаБезНДС КАК СуммаБезНДС,
    ТЧ.СуммаСНДС КАК СуммаСНДС
ИЗ
    Документ.РасчетСтоимостиВоды.ТЧРасчетСтоимостиПоСчетчикам КАК ТЧ
ГДЕ
    ТЧ.Ссылка В(&Ссылка)

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

Шаг 2. Настройка кастомной иерархии через связи наборов данных

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

Разберем по шагам процесс настройки связи наборов данных в конструкторе схемы компоновки:

  1. Перейдем на вкладку «Связи наборов данных».
  2. Добавим новую строку связи.
  3. В качестве Источника связи выберем наш единственный набор данных (например, НаборДанных1).
  4. В качестве Приемника связи выберем этот же набор данных — НаборДанных1.
  5. В поле Выражение-источник укажем реквизит, определяющий родителя: ГлавныйСчетчик.
  6. В поле Выражение-приемник укажем ключевое поле, по которому ищется родитель: Счетчик.
  7. В поле Параметры связи ничего не указываем, если нам не нужно накладывать дополнительные фильтры.
  8. В поле Начальное значение связи необходимо прописать условие для корневых элементов дерева. Как правило, для вершин дерева значением поля ГлавныйСчетчик является пустая ссылка или значение НЕОПРЕДЕЛЕНО. Запишем выражение: ЗначениеЗаполнено(ГлавныйСчетчик) = Ложь или просто оставим поле пустым, чтобы СКД самостоятельно определила корни дерева.

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

Шаг 3. Настройка структуры отчета и группировок

Теперь перейдем на вкладку «Настройки» СКД для создания структуры вывода дерева.

Выполним следующие действия:

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

При такой настройке СКД сначала выведет корневой счетчик, затем под ним — подчиненные счетчики второго уровня, и так далее по цепочке рекурсии, отображая детальные показатели для каждого уровня.

Шаг 4. Решение проблемы дублирования строк родительских элементов

При построении иерархии «элемент-элемент» разработчики часто сталкиваются с неприятным эффектом: родительский элемент выводится в отчете дважды — первый раз как узел группировки (ветка дерева), а второй раз как детальная строка под самим собой. Это происходит потому, что запись о родителе присутствует в исходной табличной части документа.

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

Мы создадим два независимых набора данных-запроса в схеме компоновки:

Набор данных №1 (Данные документа) — выбирает транзакционные данные из табличной части документа:


ВЫБРАТЬ
    ТЧ.Счетчик КАК Счетчик,
    ТЧ.Итого КАК Итого,
    ТЧ.СуммаСНДС КАК СуммаСНДС
ИЗ
    Документ.РасчетСтоимостиВоды.ТЧРасчетСтоимостиПоСчетчикам КАК ТЧ
ГДЕ
    ТЧ.Ссылка В(&Ссылка)

Набор данных №2 (Структура счетчиков) — выбирает связи и иерархию непосредственно из справочника:


ВЫБРАТЬ
    Счетчики.Ссылка КАК Счетчик,
    Счетчики.ГлавныйСчетчик КАК ГлавныйСчетчик
ИЗ
    Справочник.Счетчики КАК Счетчики

Свяжем эти два набора данных на вкладке «Связи наборов данных» следующим образом:

  1. Связь от НаборДанных1 к НаборДанных2 по полю Счетчик. Это обогатит наши транзакционные данные информацией о родительских связях.
  2. Связь от НаборДанных2 к самому себе (поля ГлавныйСчетчик -> Счетчик) для рекурсивного обхода дерева.

Разделение на транзакционную часть (документ) и структурную часть (справочник) позволяет СКД корректно разделять узлы дерева и агрегировать числовые показатели на родительских уровнях без дублирования строк.

Альтернативный вариант: Иерархия по кодам (1, 1.1, 1.2)

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

  1. На вкладке «Вычисляемые поля» добавим новое поле с именем КодРодителя.
  2. В выражении этого поля напишем код, отсекающий последний сегмент строки. Для этого можно использовать строковые функции платформы. Например, выражение для поиска родителя по коду может выглядеть так:
    
    Выбор 
        Когда Найти(НомерСтрокиИерархия, ".") > 0 
        Тогда Подстрока(НомерСтрокиИерархия, 1, Найти(НомерСтрокиИерархия, ".", -1) - 1) 
        Иначе "" 
    Конец
    
    Примечание: Функция Найти с отрицательным третьим параметром позволяет искать вхождение точки с конца строки, что идеально подходит для вычисления родительского кода (для "1.2.3" родителем станет "1.2").
  3. Свяжем набор данных сам с собой, используя КодРодителя в качестве выражения-источника и исходный НомерСтрокиИерархия в качестве выражения-приемника.

Используя предложенные методы, мы получаем гибкий, масштабируемый и производительный отчет, работающий без ошибок группировки СУБД и выводящий любые иерархические структуры табличных частей документов в максимально понятном для пользователя виде.

← На главную