В аналитической отчетности часто возникает задача сравнения показателей «год к году» или «период к периоду». Однако простое сравнение по датам (например, 1-е число месяца с 1-м числом прошлого года) часто оказывается неинформативным, так как дни недели не совпадают. Для розничной торговли или сферы услуг критически важно сравнивать понедельник с понедельником, а субботу с субботой. Рассмотрим подробно, как реализовать такое сопоставление в Системе компоновки данных (СКД).
Разберем ситуацию: нам необходимо вывести продажи за текущий период и аналогичный прошлый период в одной строке, сопоставив их по дню недели и порядковому номеру недели внутри выбранного интервала. Основная проблема заключается в том, что функции НЕДЕЛЯ() в языке запросов возвращают порядковый номер недели в году, что не подходит, если наши периоды начинаются в середине года или относятся к разным годам с разным количеством недель.
Проанализируем стратегию решения. Нам необходимо:
Рассмотрим пример запроса, который позволяет вычислить индекс недели относительно даты начала, переданной в параметрах. Это гарантирует, что первая неделя первого периода всегда сопоставится с первой неделей второго периода.
Выясним причину использования функции РАЗНОСТЬДАТ. Она позволяет вычислить количество дней между началом периода и текущей датой продажи, а деление на 7 дает нам индекс недели.
ВЫБРАТЬ
ПродажиОбороты.ПериодДень КАК ПериодДень,
ДЕНЬНЕДЕЛИ(ПродажиОбороты.ПериодДень) КАК ДеньНедели,
ЦЕЛОЕ(РАЗНОСТЬДАТ(&ДатаНачала1, ПродажиОбороты.ПериодДень, ДЕНЬ) / 7) + 1 КАК НомерНедели,
ПродажиОбороты.СуммаВыручкиОборот КАК Сумма1,
0 КАК Сумма2
ПОМЕСТИТЬ ВтПериод1
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&ДатаНачала1, &ДатаОкончания1, Авто, ) КАК ПродажиОбороты
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ПродажиОбороты.ПериодДень КАК ПериодДень,
ДЕНЬНЕДЕЛИ(ПродажиОбороты.ПериодДень) КАК ДеньНедели,
ЦЕЛОЕ(РАЗНОСТЬДАТ(&ДатаНачала2, ПродажиОбороты.ПериодДень, ДЕНЬ) / 7) + 1 КАК НомерНедели,
0 КАК Сумма1,
ПродажиОбороты.СуммаВыручкиОборот КАК Сумма2
ПОМЕСТИТЬ ВтПериод2
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&ДатаНачала2, &ДатаОкончания2, Авто, ) КАК ПродажиОбороты
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ЕСТЬNULL(Вт1.НомерНедели, Вт2.НомерНедели) КАК НомерНедели,
ЕСТЬNULL(Вт1.ДеньНедели, Вт2.ДеньНедели) КАК ДеньНедели,
МАКСИМУМ(Вт1.ПериодДень) КАК Дата1,
МАКСИМУМ(Вт2.ПериодДень) КАК Дата2,
СУММА(ЕСТЬNULL(Вт1.Сумма1, 0)) КАК СуммаПериод1,
СУММА(ЕСТЬNULL(Вт2.Сумма2, 0)) КАК СуммаПериод2
ИЗ
ВтПериод1 КАК Вт1
ПОЛНОЕ СОЕДИНЕНИЕ ВтПериод2 КАК Вт2
ПО Вт1.НомерНедели = Вт2.НомерНедели
И Вт1.ДеньНедели = Вт2.ДеньНедели
СГРУППИРОВАТЬ ПО
ЕСТЬNULL(Вт1.НомерНедели, Вт2.НомерНедели),
ЕСТЬNULL(Вт1.ДеньНедели, Вт2.ДеньНедели)
В данном примере мы используем ПОЛНОЕ СОЕДИНЕНИЕ, чтобы не потерять данные, если в какой-то из периодов продаж в конкретный день недели не было. Обратите внимание на использование ЕСТЬNULL для полей группировки — это обязательное условие корректной работы полного соединения.
Если в вашей конфигурации ведется РегистрСведений.РегламентированныйПроизводственныйКалендарь, лучше использовать его как мастер-таблицу. Это позволит избежать пропусков дат (так называемых "дырок" в периодах), когда продаж не было совсем.
Разберем по шагам, как построить такой запрос:
Посмотрим на пример формирования сетки дат:
ВЫБРАТЬ
Календарь.ДатаКалендаря КАК Дата,
ДЕНЬНЕДЕЛИ(Календарь.ДатаКалендаря) КАК ДеньНедели,
РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(&ДатаНачала, НЕДЕЛЯ), НАЧАЛОПЕРИОДА(Календарь.ДатаКалендаря, НЕДЕЛЯ), ДЕНЬ) / 7 + 1 КАК НомерНедели
ИЗ
РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК Календарь
ГДЕ
Календарь.ДатаКалендаря МЕЖДУ &ДатаНачала И &ДатаОкончания
Этот подход более надежен для СКД, так как визуализация отчета будет содержать все дни недели, даже если они пустые, что сохраняет структуру таблицы "день в день".
Проанализируем ситуацию, когда мы не хотим писать один огромный запрос. В СКД можно создать два независимых набора данных (НаборДанных1 и НаборДанных2) и настроить связь между ними на вкладке "Связи наборов данных".
Для этого в каждом наборе данных должны присутствовать поля ДеньНедели и ОтносительныйНомерНедели. В настройках связей укажите:
ДеньНедели = ДеньНедели И ОтносительныйНомерНедели = ОтносительныйНомерНеделиВажный момент: Чтобы отчет вывел данные из обоих наборов, даже если в одном из них нет совпадений, убедитесь, что в структуре настроек СКД выбраны поля из обоих наборов, а иерархия группировок построена по общим полям (которые можно объединить в один Набор данных - объединение).
Чтобы пользователь понимал, какие именно даты сопоставлены в отчете, рекомендуем использовать Вычисляемые поля для формирования красивых заголовков. Например, можно создать поле ПредставлениеСтроки, которое будет объединять даты обоих периодов.
Рассмотрим выражение для вычисляемого поля:
"Неделя " + Строка(НомерНедели) + " (День " + Строка(ДеньНедели) + ")"
В настройках компоновки (вкладка "Настройки") лучше всего использовать тип макета "Таблица":
НомерНедели, а затем вложенную группировку по полю ДеньНедели.Сумма1 и Сумма2 выведены в выбранные поля) или добавить группировку по периоду, если вы использовали ОБЪЕДИНИТЬ ВСЕ в запросе.Проанализируем кейс, когда первый период — это полный месяц (31 день), а второй — тоже месяц, но в нем 28 дней (февраль). При сопоставлении по номеру недели и дню недели у вас могут остаться "лишние" дни в первом периоде, которым не найдется пары во втором.
Чтобы отчет выглядел профессионально, используйте в запросе ЕСТЬNULL(Сумма2, 0). В группировках СКД такие записи будут отображаться с заполненной датой первого периода и пустой (или нулевой) колонкой второго. Это корректное поведение для план-фактного или сравнительного анализа.
Таким образом, мы разобрали основные методы сопоставления произвольных периодов. Использование относительного индекса недели (Дата - НачалоПериода) / 7 является наиболее универсальным способом, позволяющим обойти ограничения стандартной функции НЕДЕЛЯ() и связать данные максимально точно.