Как в СКД связать набор данных с календарем и рассчитать количество дней без дублей?

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

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

Проблема дублирования данных при связи наборов 1:N

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

Для решения этой задачи рассмотрим пошаговый алгоритм настройки наборов данных и их связей.

Шаг 1. Подготовка наборов данных

В схеме компоновки данных создадим два набора данных типа «запрос».

Набор 1 (Основной): Здесь мы выбираем данные наших документов или состояний. Важно, чтобы в этом наборе были поля ДатаНачала и ДатаОкончания.

Набор 2 (Календарь): Этот запрос должен обращаться к регистру сведений ПроизводственныйКалендарь или аналогичному справочнику. Чтобы не тащить лишние строки, мы сразу в запросе можем подготовить поле для суммирования.

Рассмотрим пример запроса для набора-календаря:


ВЫБРАТЬ
    СУММА(1) КАК КоличествоВыходных,
    Календарь.Дата КАК ДатаКалендаря
ИЗ
    РегистрСведений.ПроизводственныйКалендарь КАК Календарь
ГДЕ
    Календарь.ВидДня В (ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Суббота), 
                       ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Воскресенье))
    И Календарь.Дата МЕЖДУ &НачалоПериода И &КонецПериода
ГРУППИРОВАТЬ ПО
    Календарь.Дата

Шаг 2. Настройка связей наборов данных

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

  1. В колонке «Источник связи» указываем Набор1.
  2. В колонке «Приемник связи» указываем Набор2.
  3. В условии связи связываем ДатаНачала (Источник) с параметром НачалоПериода (Приемник).
  4. Аналогично связываем ДатаОкончания (Источник) с параметром КонецПериода (Приемник).

Благодаря такой настройке, для каждого документа система «сходит» в календарь и получит количество выходных именно за его период, а не за весь период отчета.

Шаг 3. Работа с ресурсами и агрегатными функциями

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

Для числовых данных из основного документа (которые не должны множиться) лучше использовать функции Максимум() или Минимум() в контексте детальных записей. А для поля КоличествоВыходных из календаря используем Сумма().

Посмотрим на пример выражения для итогового расчета рабочих дней:

РазностьДат(ДатаНачала, ДатаОкончания, "ДЕНЬ") - ЕСТЬNULL(КоличествоВыходных, 0)

Шаг 4. Использование пользовательских полей

Как показывает практика, иногда вычисления в «Вычисляемых полях» происходят до того, как ресурсы будут агрегированы, что снова ведет к дублям. Автор темы нашел изящное решение: перенести итоговый расчет в «Пользовательские поля» (вкладка «Настройки» -> «Пользовательские поля»).

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

Альтернативные методы решения

Рассмотрим еще два способа, которые часто применяют профессиональные разработчики 1С:

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

Важные замечания по настройке связи

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

Резюмируем: для корректного расчета рабочих дней без дублей в СКД используйте передачу дат документа в параметры набора-календаря, применяйте агрегатную функцию Сумма(1) в запросе календаря и выполняйте финальные вычисления в пользовательских полях или через агрегаты Максимум() для основных показателей.

← На главную