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