При разработке отчетов в 1С часто возникает задача представить данные в виде шахматки (матрицы или кросс-таблицы): по строкам выводятся аналитические разрезы (задачи, объекты, документы), а по колонкам — дни выбранного периода (с 1 по 31 число). В ячейках на пересечении требуется отобразить ответственное лицо, статус или отметку о выполнении. Задача усложняется, когда данные в регистре задаются не единичной датой, а интервалом (дата начала и дата окончания), либо когда в один и тот же день за одну задачу отвечает несколько сотрудников.
Разберем по шагам, как решить эту задачу средствами системы компоновки данных (СКД) без сложного программного кода.
Чтобы в отчете отобразились все дни выбранного интервала (даже те, на которые не приходится записей в регистре), нам необходима непрерывная последовательность дат. Получить такую таблицу в запросе можно двумя основными путями.
Способ 1. Использование производственного календаря конфигурации
В большинстве типовых конфигураций 1С (ERP, КА, УТ, ЗУП, БП) уже присутствует справочник или регистр сведений календаря. Самый простой и производительный вариант — выбрать даты из существующего регистра:
ВЫБРАТЬ
Календарь.Дата КАК ПериодДень
ПОМЕСТИТЬ ВТ_Календарь
ИЗ
РегистрСведений.ДанныеПроизводственногоКалендаря КАК Календарь
ГДЕ
Календарь.Дата МЕЖДУ &НачалоПериода И &КонецПериода
;
Способ 2. Генерация дат декартовым произведением цифр
Если отчет разрабатывается для нетиповой конфигурации или отдельного регистра календаря нет под рукой, сформируем таблицу чисел от 0 до 9999 с помощью соединения числовых последовательностей и добавим полученные смещения к начальной дате:
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, Числа.НомерДня) КАК ПериодДень
ПОМЕСТИТЬ ВТ_Календарь
ИЗ
(ВЫБРАТЬ
aa.a * 1000 + bb.b * 100 + cc.c * 10 + dd.d КАК НомерДня
ИЗ
(ВЫБРАТЬ 0 КАК a ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК aa
ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК b ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК bb ПО (ИСТИНА)
ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК c ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК cc ПО (ИСТИНА)
ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК d ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК dd ПО (ИСТИНА)) КАК Числа
ГДЕ
Числа.НомерДня <= РАЗНОСТЬДАТ(&НачалоПериода, &КонецПериода, ДЕНЬ)
;
Рассмотрим случай, когда период ответственности задан двумя полями: ДатаНачала и ДатаОкончания. Нам нужно, чтобы запись регистра отображалась в каждой колонке дня, попадающего в указанный диапазон.
Обратите внимание на производительность: соединение по неравенству может выполняться медленно при большом объеме данных. Поэтому сначала отфильтруем записи регистра во временную таблицу по условию пересечения с отчетом, а затем соединим их с календарем:
ВЫБРАТЬ
Регистр.Измерение1 КАК Измерение1,
Регистр.Измерение2 КАК Измерение2,
Регистр.Ответственный КАК Ответственный,
НАЧАЛОПЕРИОДА(Регистр.ДатаНачала, ДЕНЬ) КАК ДатаНачала,
ВЫБОР
КОГДА Регистр.ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1) ИЛИ Регистр.ДатаОкончания ЕСТЬ NULL
ТОГДА &КонецПериода
ИНАЧЕ НАЧАЛОПЕРИОДА(Регистр.ДатаОкончания, ДЕНЬ)
КОНЕЦ КАК ДатаОкончания
ПОМЕСТИТЬ ВТ_ДанныеРегистра
ИЗ
РегистрСведений.ОтветственныеПоЗадачам КАК Регистр
ГДЕ
Регистр.ДатаНачала <= &КонецПериода
И (Регистр.ДатаОкончания >= &НачалоПериода ИЛИ Регистр.ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1))
;
ВЫБРАТЬ
Календарь.ПериодДень КАК ПериодДень,
Данные.Измерение1 КАК Измерение1,
Данные.Измерение2 КАК Измерение2,
Данные.Ответственный КАК Ответственный
ИЗ
ВТ_Календарь КАК Календарь
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ДанныеРегистра КАК Данные
ПО Календарь.ПериодДень >= Данные.ДатаНачала
И Календарь.ПериодДень <= Данные.ДатаОкончания
Важные нюансы запроса:
НАЧАЛОПЕРИОДА(..., ДЕНЬ) отсекает время, чтобы сравнение дат производилось корректно по суткам.ДАТАВРЕМЯ(1, 1, 1)) обрабатывается выражением ВЫБОР КОГДА. Это гарантирует, что продолжающиеся задачи не потеряются при формировании отчета.Если на одну дату и одну задачу приходится несколько ответственных сотрудников, стандартные функции МАКСИМУМ() или МИНИМУМ() не подходят, так как они выведут только одного человека по алфавиту.
В СКД есть встроенная функция для объединения строк. Перейдем на вкладку «Ресурсы» схемы компоновки данных и добавим ресурс Ответственный со следующим выражением:
СоединитьСтроки(Массив(Различные Ответственный), Символы.ПС)
Разберем, как работает это выражение:
Массив(Различные Ответственный) собирает всех ответственных в текущей группировке в единую коллекцию, автоматически исключая повторяющиеся записи.СоединитьСтроки(...) объединяет элементы массива через заданный разделитель. В качестве разделителя можно указать Символы.ПС (перенос строки, чтобы сотрудники выводились друг под другом) или обычную запятую с пробелом: ", ".Для вывода сетки настроим структуру отчета на вкладке «Настройки»:
Измерение1, затем Измерение2).ПериодДень.Ответственный.Чтобы в шапке отчета вместо полного представления даты (например, «01.09.2023 00:00:00») выводился только номер дня месяца или короткая дата, перейдем на вкладку «Наборы данных» (или вкладку «Условное оформление») и для поля ПериодДень укажем в параметре «Оформление» строку формата: ДФ=dd (только число) или ДФ=dd.MM (число и месяц).
В результате СКД сформирует наглядную матрицу, в которой интервалы автоматически развернуты по календарным дням, а все назначенные сотрудники отображаются в ячейках таблицы без необходимости ручной постобработки в модуле отчета.