Как правильно сгруппировать данные по дням в отчете СКД при соединении плана и факта?

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

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

Анализируем причину «раздвоения» строк

Проблема часто кроется в механизме ПОЛНОГО СОЕДИНЕНИЯ или ЛЕВОГО СОЕДИНЕНИЯ в запросе. Если мы соединяем две таблицы по полям Сотрудник и День, но при этом в одной из таблиц есть дополнительные поля (например, СтатьяЗатрат, НоменклатурнаяГруппа или Подразделение), которых нет в другой или они имеют значение NULL, система компоновки данных (СКД) воспримет их как разные комбинации измерений и создаст отдельные строки.

Еще одна распространенная причина — несовпадение типов или точности дат. Если в одной таблице дата — это 01.07.2023 00:00:00, а в другой — 01.07.2023 14:30:15, то для СКД это разные периоды. Всегда приводите даты к началу дня с помощью функции НАЧАЛОПЕРИОДА(Дата, ДЕНЬ).

Метод логического соединения таблиц (План + Факт)

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


// ВТ_План - получаем данные штатного расписания
ВЫБРАТЬ
    ВТ.Сотрудник КАК Сотрудник,
    ВТ.День КАК День,
    СУММА(ВТ.Часов) КАК ЧасовПлан
ПОМЕСТИТЬ ВТ_План
ИЗ
    ИсточникПлана КАК ВТ
СГРУППИРОВАТЬ ПО
    ВТ.Сотрудник,
    ВТ.День;

////////////////////////////////////////////////////////////////////////////////
// ВТ_Факт - получаем данные из документов посещений
ВЫБРАТЬ
    Посещения.Сотрудник КАК Сотрудник,
    НАЧАЛОПЕРИОДА(Посещения.Период, ДЕНЬ) КАК День,
    СУММА(Посещения.Количество) КАК ЧасовФакт
ПОМЕСТИТЬ ВТ_Факт
ИЗ
    Документ.Посещения КАК Посещения
ГДЕ
    Посещения.Проведен
СГРУППИРОВАТЬ ПО
    Посещения.Сотрудник,
    НАЧАЛОПЕРИОДА(Посещения.Период, ДЕНЬ);

Теперь самое важное — финальное объединение. Чтобы не потерять данные, если сотрудника нет в одной из таблиц, используем ПОЛНОЕ СОЕДИНЕНИЕ и функцию ЕСТЬNULL для полей группировки:


ВЫБРАТЬ
    ЕСТЬNULL(ВТ_План.Сотрудник, ВТ_Факт.Сотрудник) КАК Сотрудник,
    ЕСТЬNULL(ВТ_План.День, ВТ_Факт.День) КАК День,
    ВТ_План.ЧасовПлан КАК ЧасовПлан,
    ВТ_Факт.ЧасовФакт КАК ЧасовФакт
ИЗ
    ВТ_План КАК ВТ_План
        ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Факт КАК ВТ_Факт
        ПО ВТ_План.Сотрудник = ВТ_Факт.Сотрудник
            И ВТ_План.День = ВТ_Факт.День

Обработка «горизонтальных» данных (31 колонка с часами)

Часто в конфигурациях 1С данные штатного расписания хранятся в «горизонтальном» виде: один документ содержит колонки Часов1, Часов2... Часов31. Для СКД такую структуру необходимо нормализовать (перевести колонки в строки). Рассмотрим, как это сделать программно в процедуре ПриКомпоновкеРезультата, чтобы затем передать данные в СКД через менеджер временных таблиц.

Проанализируем пример кода, который обходит колонки документа и формирует вертикальную таблицу:


// Создаем таблицу значений для хранения развернутых данных
тзШтатноеРасписание = Новый ТаблицаЗначений;
тзШтатноеРасписание.Колонки.Добавить("Сотрудник");
тзШтатноеРасписание.Колонки.Добавить("День", Новый ОписаниеТипов("Дата"));
тзШтатноеРасписание.Колонки.Добавить("Часов", Новый ОписаниеТипов("Число"));

// Выбираем данные из документа (например, ШтатноеРасписаниеУпр)
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ * ИЗ Документ.ШтатноеРасписаниеУпр.Сотрудники ГДЕ Ссылка.Проведен";
Результат = Запрос.Выполнить().Выбрать();

Пока Результат.Следующий() Цикл
    Для ит = 1 По 31 Цикл
        ИмяПоля = "Часов" + ит;
        ЗначениеЧасов = Результат[ИмяПоля];
        
        Если ЗначениеЧасов > 0 Тогда
            НоваяСтрока = тзШтатноеРасписание.Добавить();
            НоваяСтрока.Сотрудник = Результат.Сотрудник;
            // Важно: корректно вычисляем дату дня
            НоваяСтрока.День = НачалоМесяца(Результат.Ссылка.ПериодРегистрации) + (ит - 1) * 86400;
            НоваяСтрока.Часов = ЗначениеЧасов;
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Внимание: при расчете даты через прибавление секунд (86400 * (ит-1)), как показано в примере выше, будьте осторожны с переходом месяцев (например, 31-е число в феврале). Рекомендуется добавить проверку Месяц(НоваяСтрока.День) = Месяц(НачалоМесяца), чтобы не захватить лишние дни следующего месяца.

Настройка СКД для корректной группировки

После того как данные получены в правильном формате (одна строка — один день), необходимо настроить саму схему компоновки данных:

  1. Роли полей: Перейдите на закладку «Наборы данных». Для поля День установите роль Период и укажите тип периода День. Это подскажет СКД, как правильно упорядочивать и объединять данные по времени.
  2. Ресурсы: Поля ЧасовПлан и ЧасовФакт должны быть добавлены на закладку «Ресурсы» с функцией СУММА(). Это позволит системе «схлопнуть» записи, если они все же продублируются из-за разных аналитик.
  3. Настройки группировки: В настройках отчета (закладка «Настройки») убедитесь, что группировка идет сначала по Сотруднику, а затем по Дню. Проверьте, что в выбранных полях группировки День не добавлены лишние детальные записи (например, время создания документа), которые могут «разбивать» строку.

Решение проблемы с аналитикой (Номенклатурные группы)

Если в фактических данных заполнена НоменклатурнаяГруппа (или «Аналитика»), а в плановых — нет, СКД выведет их в две строки: одну с заполненной аналитикой и фактом, вторую — с пустой аналитикой и планом. Выясним, как этого избежать:

Проанализируем ситуацию: если аналитика важна только для факта, попробуйте убрать её из группировок верхнего уровня. Если же нужно видеть план и факт в одной строке вместе с аналитикой, то в запросе для таблицы плана нужно либо подставить «пустое» значение аналитики по умолчанию, либо использовать функцию ЕСТЬNULL(Аналитика, "Общая"), чтобы значения совпали.

Использование Менеджера временных таблиц

Если вы подготавливаете данные в коде (как в разделе про 31 колонку), передавайте их в СКД через МенеджерВременныхТаблиц. Это позволит основному запросу СКД видеть вашу подготовленную таблицу тзШтатноеРасписание так, будто это реальная таблица базы данных.


МВТ = Новый МенеджерВременныхТаблиц;
ЗапросПодготовки = Новый Запрос("ВЫБРАТЬ * ПОМЕСТЬИТЬ ВТШтатное ИЗ &ТЗ КАК ТЗ");
ЗапросПодготовки.УстановитьПараметр("ТЗ", тзШтатноеРасписание);
ЗапросПодготовки.МенеджерВременныхТаблиц = МВТ;
ЗапросПодготовки.Выполнить();

// При инициализации процессора компоновки передаем МВТ
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, ДанныеРасшифровки, Истина, , МВТ);

Такой подход гарантирует, что вы сможете выполнить сложные соединения (JOIN) между внешними данными и данными из таблиц документов прямо внутри SQL-движка, что обеспечит максимальную производительность и корректность группировки по дням.

← На главную