Как правильно создавать и выводить динамические колонки в таблице значений по дням периода

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

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

Разбор ошибки именования динамических колонок

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

  1. Имя должно начинаться с буквы или символа подчеркивания.
  2. Имя не может содержать пробелы, точки, тире и другие специальные символы.
  3. Имя не может состоять только из цифр.

Для решения этой проблемы мы должны формировать техническое имя с префиксом, а человекочитаемую дату выносить во второй параметр — Заголовок. Рассмотрим пример правильного кода:


КолвоДней = (КонецПериода - НачалоПериода) / (24 * 60 * 60) + 1;
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Объект", Новый ОписаниеТипов("СправочникСсылка.Номера"));

Для н = 0 по КолвоДней - 1 Цикл
    ТекущаяДата = НачалоПериода + (н * 24 * 60 * 60);
    // Формируем уникальное техническое имя без спецсимволов
    ИмяКолонки = "День_" + Формат(ТекущаяДата, "ДФ=yyyyMMdd");
    // Формируем красивый заголовок для пользователя
    ЗаголовокКолонки = Формат(ТекущаяДата, "ДФ='д МММ гг'");
    
    ТЗ.Колонки.Добавить(ИмяКолонки, Новый ОписаниеТипов("Число"), ЗаголовокКолонки);
КонецЦикла;

Как обращаться к динамическим колонкам в цикле

После того как мы создали структуру таблицы, нам нужно заполнить её данными. Выясним, как обращаться к ячейкам, если мы не знаем точных имен колонок заранее. В 1С предусмотрено два основных способа обращения к колонке строки ТаблицыЗначений:

  1. По имени: НоваяСтрока["День_20231001"] = 10;
  2. По индексу: НоваяСтрока[ИндексКолонки] = 10; (индексация начинается с 0).

Проанализируем ситуацию заполнения. Использовать индекс удобно, если вы точно знаете порядок добавления колонок. Однако безопаснее использовать обращение по сформированному имени:


Для Каждого СтрокаДанных Из РезультатЗапроса Цикл
    НоваяСтрока = ТЗ.Добавить();
    НоваяСтрока.Объект = СтрокаДанных.Номер;
    
    // Вычисляем имя колонки на основании даты из данных
    ИмяКолонки = "День_" + Формат(СтрокаДанных.ДатаСобытия, "ДФ=yyyyMMdd");
    
    // Проверяем наличие колонки (на всякий случай) и записываем значение
    Если ТЗ.Колонки.Найти(ИмяКолонки) <> Неопределено Тогда
        НоваяСтрока[ИмяКолонки] = СтрокаДанных.Количество;
    КонецЕсли;
КонецЦикла;

Оптимизация: получение данных одним запросом

Важный момент, на котором стоит остановиться: никогда не выполняйте запрос внутри цикла по дням периода. Если в периоде 30 дней, а объектов 100, вы выполните 3000 обращений к базе данных, что катастрофически замедлит систему. Выясним, как сделать это правильно.

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

Если же нам нужно получить список всех дат периода прямо в запросе (например, чтобы показать дни, в которых не было движений), воспользуемся методом соединения с виртуальной таблицей чисел или календарем. Посмотрим на пример генерации последовательности дат в запросе:


ВЫБРАТЬ
    ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, aa.a * 10 + bb.b) КАК Период
ИЗ
    (ВЫБРАТЬ 0 КАК a ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3) КАК aa
    ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК b ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 
    ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 
    ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК bb
    ПО (ИСТИНА)
ГДЕ
    ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, aa.a * 10 + bb.b) <= &КонецПериода

Этот запрос генерирует таблицу дат от 0 до 39 дней от начальной даты. Соединив её левым соединением с вашими данными, вы получите готовую выборку для заполнения отчета.

Вывод динамической таблицы в отчет (Табличный документ)

Когда ТаблицаЗначений заполнена, её нужно красиво вывести пользователю. Если мы используем обычный вывод через ПостроительОтчета или программный обход, важно не забыть про оформление (сетку и границы ячеек). Выясним, как программно отрисовать границы.

Проанализируем пример вывода с использованием областей ТабличногоДокумента:


ТабДок = Новый ТабличныйДокумент;
// Создаем форматную линию
ТонкаяЛиния = Новый Линия(ТипЛинииЯчейкиТабличногоДокумента.Сплошная, 1);

// Выводим шапку
Для Каждого Колонка Из ТЗ.Колонки Цикл
    Область = ТабДок.Область(1, ТЗ.Колонки.Индекс(Колонка) + 1);
    Область.Текст = ?(ЗначениеЗаполнено(Колонка.Заголовок), Колонка.Заголовок, Колонка.Имя);
    Область.Обвести(ТонкаяЛиния, ТонкаяЛиния, ТонкаяЛиния, ТонкаяЛиния);
    Область.ЦветФона = ЦветаСтиля.ЦветФонаШапкиТаблицы;
КонецЦикла;

// Выводим данные
НомерСтрокиДок = 2;
Для Каждого СтрТЗ Из ТЗ Цикл
    Для Каждого Колонка Из ТЗ.Колонки Цикл
        Область = ТабДок.Область(НомерСтрокиДок, ТЗ.Колонки.Индекс(Колонка) + 1);
        Область.Текст = СтрТЗ[Колонка.Имя];
        Область.Обвести(ТонкаяЛиния, ТонкаяЛиния, ТонкаяЛиния, ТонкаяЛиния);
    КонецЦикла;
    НомерСтрокиДок = НомерСтрокиДок + 1;
КонецЦикла;

Альтернатива: использование Системы Компоновки Данных (СКД)

Рассмотрим более современный и эффективный метод решения данной задачи — использование СКД. Вместо того чтобы вручную мучиться с именами колонок в ТЗ, мы можем настроить отчет типа «Таблица» в компоновке данных.

Разберем алгоритм настройки:

  1. Создадим набор данных (запрос), который возвращает три поля: Объект, Дата и Показатель.
  2. На вкладке «Настройки» добавим элемент «Таблица».
  3. В Строки таблицы добавим группировку по полю Объект.
  4. В Колонки таблицы добавим группировку по полю Дата.
  5. В Выбранные поля добавим Показатель (сумма).

Система самостоятельно создаст столько колонок, сколько различных дат встретится в результате запроса. При этом СКД автоматически подставит правильные заголовки, подведет итоги и нарисует сетку. Это избавляет разработчика от написания сотен строк кода по управлению интерфейсом таблицы.

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

Таким образом, мы рассмотрели как ручной способ управления динамическими колонками через ТаблицуЗначений, так и автоматизированный через СКД. Выбор метода зависит от сложности бизнес-логики, но для визуализации данных предпочтительнее использовать механизмы компоновки.

← На главную