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

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

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

Выясним причину: почему возникают ошибки при работе со списками в запросе?

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


ВЫБРАТЬ
    &РасчетыЗаТеплоэнергиюРег КАК РасчетыЗаТеплоэнергиюРег,
    СуммаОстаток КАК Сумма
ИЗ
    РегистрНакопления.Взаиморасчеты.Остатки
СГРУППИРОВАТЬ ПО
    &РасчетыЗаТеплоэнергиюРег

Такой запрос вернет ошибку: «В запросе, содержащем предложение СГРУППИРОВАТЬ ПО, в списке выборки не могут участвовать поля неограниченной длины». Попытка исправить ситуацию с помощью функции выразить ВЫРАЗИТЬ(&РасчетыЗаТеплоэнергиюРег КАК СТРОКА(1000)) приводит к другой ошибке: «Несовместимые типы ВЫРАЗИТЬ».

Это происходит по следующим причинам:

  1. Ограничение типа данных СУБД: Объект СписокЗначений — это коллекция значений во встроенном языке 1С. База данных (SQL) ничего не знает о таком типе данных. На уровне СУБД мы не можем выбрать «список» в качестве конкретного поля записи или сгруппировать по нему строки таблицы.
  2. Работа функции ВЫРАЗИТЬ: Функция встроенного языка запросов ВЫРАЗИТЬ предназначена для работы с примитивными типами данных (числа, строки, даты, булево) или ссылочными типами конкретных справочников/документов. Преобразовать сложный объект типа «коллекция» в строку на уровне SQL-запроса невозможно, отсюда и ошибка совместимости типов.
  3. Неопределенность СКД: Когда компоновка данных не может сопоставить тип параметра с типами метаданных, она считает его полем неограниченной длины. СУБД физически не может строить индексы и выполнять группировку по таким полям.

Теперь рассмотрим рабочие и профессиональные методы решения этой задачи.

Решение 1. Использование конструкции ВЫБОР (CASE) в запросе

Если у нас есть конечное и небольшое количество списков (например, 2–5 групп), самым быстрым и простым решением будет использование логических условий прямо в тексте запроса. Мы передаем списки значений в параметры запроса, а в поле выборки определяем текстовую или ссылочную категорию.

Разберем этот метод по шагам на практическом примере:


ВЫБРАТЬ
    ВЫБОР
        КОГДА Взаиморасчеты.ВидДеятельности В (&СписокРег) 
            ТОГДА "Расчеты за теплоэнергию (рег.)"
        КОГДА Взаиморасчеты.ВидДеятельности В (&СписокНеРег) 
            ТОГДА "Расчеты за теплоэнергию (нерег.)"
        Иначе "Прочие расчеты"
    КОНЕЦ КАК КатегорияРасчетов,
    Взаиморасчеты.СуммаОстаток КАК СуммаОстаток
ИЗ
    РегистрНакопления.Взаиморасчеты.Остатки КАК Взаиморасчеты

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

Решение 2. Использование временных таблиц для большого количества групп

Что делать, если групп классификации много? В сообщении темы автор упоминает, что таких строк-групп у него около 25. Если мы начнем писать конструкцию ВЫБОР для 25 различных списков значений, наш запрос превратится в огромный нечитаемый код, производительность которого будет крайне низкой, а поддерживать его станет настоящим кошмаром.

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

  1. Создадим во встроенном языке таблицу значений, которая будет картой сопоставления каждого элемента справочника его группе.
  2. Передадим эту таблицу в запрос СКД в качестве внешнего набора данных или поместим её во временную таблицу.
  3. Выполним левое соединение нашей основной таблицы данных с таблицей сопоставления.

Пример кода на встроенном языке для подготовки такой таблицы:


// Создаем таблицу соответствия
ТаблицаГруппировки = Новый ТаблицаЗначений;
ТаблицаГруппировки.Колонки.Добавить("ВидДеятельности", Новый ОписаниеТипов("СправочникСсылка.ВидыДеятельности"));
ТаблицаГруппировки.Колонки.Добавить("ИмяГруппы", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(150)));

// Пример заполнения (в реальной задаче данные могут извлекаться из настроек или реестров)
// Группа 1
Для Каждого ЭлементИзСписка1 Из СписокРег Цикл
    НоваяСтрока = ТаблицаГруппировки.Добавить();
    НоваяСтрока.ВидДеятельности = ЭлементИзСписка1.Значение;
    НоваяСтрока.ИмяГруппы = "Расчеты за теплоэнергию (рег)";
КонецЦикла;

// Группа 2
Для Каждого ЭлементИзСписка2 Из СписокНеРег Цикл
    НоваяСтрока = ТаблицаГруппировки.Добавить();
    НоваяСтрока.ВидДеятельности = ЭлементИзСписка2.Значение;
    НоваяСтрока.ИмяГруппы = "Расчеты за теплоэнергию (нерег)";
КонецЦикла;

Затем в схеме компоновки данных мы можем использовать менеджер временных таблиц или связать два набора данных (Набор данных - запрос и Набор данных - объект). Посмотрим, как будет выглядеть запрос, если мы передаем таблицу соответствий как параметр-таблицу (доступно при работе с СУБД через менеджер временных таблиц в коде вызова отчета):


ВЫБРАТЬ
    ТаблицаСопоставления.ВидДеятельности КАК ВидДеятельности,
    ТаблицаСопоставления.ИмяГруппы КАК ИмяГруппы
ПОМЕСТИТЬ ВТ_Сопоставление Групп
ИЗ
    &ТаблицаГруппировки КАК ТаблицаСопоставления
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ЕСТЬNULL(ВТ_Сопоставление.ИмяГруппы, "Не определено") КАК КатегорияРасчетов,
    СУММА(Взаиморасчеты.СуммаОстаток) КАК СуммаОстаток
ИЗ
    РегистрНакопления.Взаиморасчеты.Остатки КАК Взаиморасчеты
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Сопоставление Групп КАК ВТ_Сопоставление
        ПО Взаиморасчеты.ВидДеятельности = ВТ_Сопоставление.ВидДеятельности
СГРУППИРОВАТЬ ПО
    ЕСТЬNULL(ВТ_Сопоставление.ИмяГруппы, "Не определено")

Этот способ является наиболее масштабируемым, профессиональным и производительным при работе с большим объемом динамических группировок.

Решение 3. Использование механизма дополнительных реквизитов и сведений

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

Рекомендуем сделать следующее:


ВЫБРАТЬ
    Взаиморасчеты.ВидДеятельности.КатегорияДеятельности КАК КатегорияРасчетов,
    Взаиморасчеты.СуммаОстаток КАК СуммаОстаток
ИЗ
    РегистрНакопления.Взаиморасчеты.Остатки КАК Взаиморасчеты

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

Решение 4. Использование вычисляемых полей в СКД

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

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

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

Функция ОпределитьГруппуВидаДеятельности(СсылкаНаВидДеятельности) Экспорт
    // Здесь может быть ваша логика анализа списков значений
    Если СсылкаНаВидДеятельности.Регулируемый Тогда
        Возврат "Расчеты за теплоэнергию (рег)";
    Иначе
        Возврат "Расчеты за теплоэнергию (нерег)";
    КонецЕсли;
КонецФункции
  1. В схеме компоновки данных переходим на вкладку «Вычисляемые поля».
  2. Добавляем новое поле с именем КатегорияРасчетов.
  3. В колонке «Выражение» прописываем вызов нашей функции: ОбщегоНазначенияКлиентСервер.ОпределитьГруппуВидаДеятельности(ВидДеятельности).

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

Подведем итоги

Мы детально проанализировали проблему группировки по спискам значений в СКД и рассмотрели четыре различных способа решения задачи. Выбор конкретного метода зависит от архитектуры вашего решения:

← На главную