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