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