При разработке отчетов часто возникает задача вывести данные в виде шахматки или кросс-таблицы, где строками являются объекты (например, номера в гостинице или сотрудники), а колонками — дни выбранного периода. Начинающие разработчики часто сталкиваются с ошибкой «Неверное имя колонки» при попытке программного добавления колонок в ТаблицуЗначений. В этой статье мы разберем, как правильно формировать структуру таблицы, управлять именами и заголовками, а также рассмотрим оптимальные способы заполнения и вывода таких данных.
Проанализируем ситуацию: когда мы пытаемся добавить колонку, используя просто порядковый номер дня, например, ТЗ.Колонки.Добавить(Строка(н)), платформа выдает ошибку. Это происходит потому, что в методе Добавить() первый параметр — это Имя колонки. К именам объектов в 1С (идентификаторам) предъявляются строгие требования:
Для решения этой проблемы мы должны формировать техническое имя с префиксом, а человекочитаемую дату выносить во второй параметр — Заголовок. Рассмотрим пример правильного кода:
КолвоДней = (КонецПериода - НачалоПериода) / (24 * 60 * 60) + 1;
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Объект", Новый ОписаниеТипов("СправочникСсылка.Номера"));
Для н = 0 по КолвоДней - 1 Цикл
ТекущаяДата = НачалоПериода + (н * 24 * 60 * 60);
// Формируем уникальное техническое имя без спецсимволов
ИмяКолонки = "День_" + Формат(ТекущаяДата, "ДФ=yyyyMMdd");
// Формируем красивый заголовок для пользователя
ЗаголовокКолонки = Формат(ТекущаяДата, "ДФ='д МММ гг'");
ТЗ.Колонки.Добавить(ИмяКолонки, Новый ОписаниеТипов("Число"), ЗаголовокКолонки);
КонецЦикла;
После того как мы создали структуру таблицы, нам нужно заполнить её данными. Выясним, как обращаться к ячейкам, если мы не знаем точных имен колонок заранее. В 1С предусмотрено два основных способа обращения к колонке строки ТаблицыЗначений:
НоваяСтрока["День_20231001"] = 10;НоваяСтрока[ИндексКолонки] = 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;
КонецЦикла;
Рассмотрим более современный и эффективный метод решения данной задачи — использование СКД. Вместо того чтобы вручную мучиться с именами колонок в ТЗ, мы можем настроить отчет типа «Таблица» в компоновке данных.
Разберем алгоритм настройки:
Объект, Дата и Показатель.Объект.Дата.Показатель (сумма).Система самостоятельно создаст столько колонок, сколько различных дат встретится в результате запроса. При этом СКД автоматически подставит правильные заголовки, подведет итоги и нарисует сетку. Это избавляет разработчика от написания сотен строк кода по управлению интерфейсом таблицы.
Важный совет: если вам нужно вывести все дни месяца, даже если по ним нет данных, в настройках группировки по дате установите свойство «Тип дополнения» в значение «День», а также укажите начало и конец периода. Тогда СКД сама «дорисует» пустые колонки для дат, отсутствующих в выборке.
Таким образом, мы рассмотрели как ручной способ управления динамическими колонками через ТаблицуЗначений, так и автоматизированный через СКД. Выбор метода зависит от сложности бизнес-логики, но для визуализации данных предпочтительнее использовать механизмы компоновки.