При разработке отчетов на Системе компоновки данных (СКД) в 1С программисты часто сталкиваются с необходимостью классифицировать и группировать данные по определенным сегментам. Типичный пример: нам нужно разбить дебиторскую задолженность по направлениям деятельности, где каждое направление определяется своим списком значений (например, регулируемые и нерегулируемые расчеты за теплоэнергию). Попытка передать готовый список значений (объект СписокЗначений) напрямую в список выборки запроса для последующей группировки приводит к системным ошибкам. В этой статье мы подробно разберем, почему возникают эти ошибки и какими способами можно решить эту задачу правильно и эффективно.
Давайте проанализируем, почему стандартные и интуитивные действия разработчика вызывают ошибки СУБД. В первом сообщении темы автор пытается указать параметр в выборке запроса:
ВЫБРАТЬ
&РасчетыЗаТеплоэнергиюРег КАК РасчетыЗаТеплоэнергиюРег,
СуммаОстаток КАК Сумма
ИЗ
РегистрНакопления.Взаиморасчеты.Остатки
СГРУППИРОВАТЬ ПО
&РасчетыЗаТеплоэнергиюРег
Такой запрос вернет ошибку: «В запросе, содержащем предложение СГРУППИРОВАТЬ ПО, в списке выборки не могут участвовать поля неограниченной длины». Попытка исправить ситуацию с помощью функции выразить ВЫРАЗИТЬ(&РасчетыЗаТеплоэнергиюРег КАК СТРОКА(1000)) приводит к другой ошибке: «Несовместимые типы ВЫРАЗИТЬ».
Это происходит по следующим причинам:
СписокЗначений — это коллекция значений во встроенном языке 1С. База данных (SQL) ничего не знает о таком типе данных. На уровне СУБД мы не можем выбрать «список» в качестве конкретного поля записи или сгруппировать по нему строки таблицы.ВЫРАЗИТЬ предназначена для работы с примитивными типами данных (числа, строки, даты, булево) или ссылочными типами конкретных справочников/документов. Преобразовать сложный объект типа «коллекция» в строку на уровне SQL-запроса невозможно, отсюда и ошибка совместимости типов.Теперь рассмотрим рабочие и профессиональные методы решения этой задачи.
Если у нас есть конечное и небольшое количество списков (например, 2–5 групп), самым быстрым и простым решением будет использование логических условий прямо в тексте запроса. Мы передаем списки значений в параметры запроса, а в поле выборки определяем текстовую или ссылочную категорию.
Разберем этот метод по шагам на практическом примере:
ВЫБРАТЬ
ВЫБОР
КОГДА Взаиморасчеты.ВидДеятельности В (&СписокРег)
ТОГДА "Расчеты за теплоэнергию (рег.)"
КОГДА Взаиморасчеты.ВидДеятельности В (&СписокНеРег)
ТОГДА "Расчеты за теплоэнергию (нерег.)"
Иначе "Прочие расчеты"
КОНЕЦ КАК КатегорияРасчетов,
Взаиморасчеты.СуммаОстаток КАК СуммаОстаток
ИЗ
РегистрНакопления.Взаиморасчеты.Остатки КАК Взаиморасчеты
В настройках СКД на вкладке «Настройки» мы можем спокойно выбрать поле КатегорияРасчетов в качестве поля группировки. В параметры СКД &СписокРег и &СписокНеРег мы передаем соответствующие объекты СписокЗначений, содержащие элементы справочника ВидыДеятельности.
Что делать, если групп классификации много? В сообщении темы автор упоминает, что таких строк-групп у него около 25. Если мы начнем писать конструкцию ВЫБОР для 25 различных списков значений, наш запрос превратится в огромный нечитаемый код, производительность которого будет крайне низкой, а поддерживать его станет настоящим кошмаром.
Для таких сценариев оптимально использовать передачу таблицы соответствий через временную таблицу. Разберем этот алгоритм подробнее:
Пример кода на встроенном языке для подготовки такой таблицы:
// Создаем таблицу соответствия
ТаблицаГруппировки = Новый ТаблицаЗначений;
ТаблицаГруппировки.Колонки.Добавить("ВидДеятельности", Новый ОписаниеТипов("СправочникСсылка.ВидыДеятельности"));
ТаблицаГруппировки.Колонки.Добавить("ИмяГруппы", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(150)));
// Пример заполнения (в реальной задаче данные могут извлекаться из настроек или реестров)
// Группа 1
Для Каждого ЭлементИзСписка1 Из СписокРег Цикл
НоваяСтрока = ТаблицаГруппировки.Добавить();
НоваяСтрока.ВидДеятельности = ЭлементИзСписка1.Значение;
НоваяСтрока.ИмяГруппы = "Расчеты за теплоэнергию (рег)";
КонецЦикла;
// Группа 2
Для Каждого ЭлементИзСписка2 Из СписокНеРег Цикл
НоваяСтрока = ТаблицаГруппировки.Добавить();
НоваяСтрока.ВидДеятельности = ЭлементИзСписка2.Значение;
НоваяСтрока.ИмяГруппы = "Расчеты за теплоэнергию (нерег)";
КонецЦикла;
Затем в схеме компоновки данных мы можем использовать менеджер временных таблиц или связать два набора данных (Набор данных - запрос и Набор данных - объект). Посмотрим, как будет выглядеть запрос, если мы передаем таблицу соответствий как параметр-таблицу (доступно при работе с СУБД через менеджер временных таблиц в коде вызова отчета):
ВЫБРАТЬ
ТаблицаСопоставления.ВидДеятельности КАК ВидДеятельности,
ТаблицаСопоставления.ИмяГруппы КАК ИмяГруппы
ПОМЕСТИТЬ ВТ_Сопоставление Групп
ИЗ
&ТаблицаГруппировки КАК ТаблицаСопоставления
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ЕСТЬNULL(ВТ_Сопоставление.ИмяГруппы, "Не определено") КАК КатегорияРасчетов,
СУММА(Взаиморасчеты.СуммаОстаток) КАК СуммаОстаток
ИЗ
РегистрНакопления.Взаиморасчеты.Остатки КАК Взаиморасчеты
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Сопоставление Групп КАК ВТ_Сопоставление
ПО Взаиморасчеты.ВидДеятельности = ВТ_Сопоставление.ВидДеятельности
СГРУППИРОВАТЬ ПО
ЕСТЬNULL(ВТ_Сопоставление.ИмяГруппы, "Не определено")
Этот способ является наиболее масштабируемым, профессиональным и производительным при работе с большим объемом динамических группировок.
Часто программисты пытаются решить задачу «в лоб» через код, забывая о штатных возможностях платформы 1С. Если ваши 25 списков значений — это не случайный набор элементов, а устоявшиеся категории классификации видов деятельности, то самым правильным архитектурным решением будет уйти от жесткого кодирования списков в программе.
Рекомендуем сделать следующее:
ВидыДеятельности дополнительный реквизит (например, через конфигуратор или механизм «Дополнительные реквизиты и сведения» в режиме 1С:Предприятие) с названием КатегорияДеятельности.
ВЫБРАТЬ
Взаиморасчеты.ВидДеятельности.КатегорияДеятельности КАК КатегорияРасчетов,
Взаиморасчеты.СуммаОстаток КАК СуммаОстаток
ИЗ
РегистрНакопления.Взаиморасчеты.Остатки КАК Взаиморасчеты
Этот подход имеет колоссальное преимущество: при изменении законодательства или внутренней методологии учета пользователю или администратору системы будет достаточно изменить категорию у элемента справочника. При этом не придется переписывать код отчета или менять параметры его запуска.
Если вам все же необходимо определять вхождение элементов в списки на лету, но вы не хотите усложнять текст SQL-запроса, можно перенести эту логику на уровень самой СКД с помощью вкладки «Вычисляемые поля».
Для этого выполним следующие действия:
ОбщегоНазначенияКлиентСервер), которая будет принимать ссылку на элемент и возвращать имя группы. Функция должна быть доступна на сервере.
Функция ОпределитьГруппуВидаДеятельности(СсылкаНаВидДеятельности) Экспорт
// Здесь может быть ваша логика анализа списков значений
Если СсылкаНаВидДеятельности.Регулируемый Тогда
Возврат "Расчеты за теплоэнергию (рег)";
Иначе
Возврат "Расчеты за теплоэнергию (нерег)";
КонецЕсли;
КонецФункции
КатегорияРасчетов.ОбщегоНазначенияКлиентСервер.ОпределитьГруппуВидаДеятельности(ВидДеятельности).Важное предупреждение о производительности: Использование функций общих модулей в вычисляемых полях СКД приводит к построчной обработке данных на сервере приложений 1С. Если в вашем отчете выводятся десятки тысяч строк, это может существенно замедлить формирование отчета по сравнению с группировкой на уровне СУБД (Решение 1 и Решение 2).
Мы детально проанализировали проблему группировки по спискам значений в СКД и рассмотрели четыре различных способа решения задачи. Выбор конкретного метода зависит от архитектуры вашего решения:
ВЫБОР КОГДА ... В (&Список) в запросе.ТаблицуЗначений и временные таблицы в запросе.