Урок 27 · 9 мин чтения

События в процессе (открытые на дату)

Сколько заказов открыто на каждую дату, когда у события есть начало и конец. Паттерн «events in progress».

Есть класс задач, где у события два момента: начало и конец. Заказ оформлен и отгружен; договор открыт и закрыт; сотрудник нанят и уволен. Вопрос — «сколько событий активно на каждую дату» — кажется простым, но обычным COUNTROWS не решается. Это паттерн events in progress. Разбираем по DAX Patterns SQLBI.

На каком примере

Учебный набор — завершённые продажи. Поэтому пример на гипотетической таблице Заказы со столбцами ДатаОформления и ДатаОтгрузки. Логика та же для подписок, договоров, тикетов.

Почему это нетривиально

Каждый заказ — одна строка с двумя датами. А вопрос — про каждый день между ними: на 5 марта заказ ещё открыт, на 10-е уже отгружен. То есть одна строка факта должна «отметиться» на множестве дат. Прямой связи «заказ → дата» для этого мало.

Идея: дата между началом и концом

Событие активно на дату D, если оно началось не позже D и ещё не закончилось на D:

ДатаОформления <= D  И  ( ДатаОтгрузки > D  ИЛИ  ДатаОтгрузки пуста )

Считаем такие строки для каждой даты из календаря. Календарь здесь — «движок»: он перебирает даты, а мера на каждой считает, сколько заказов попадает в интервал.

Мера

Открытые заказы =
VAR _D = MAX ( Календарь[Дата] )
RETURN
    CALCULATE (
        COUNTROWS ( Заказы ),
        FILTER (
            ALL ( Заказы ),
            Заказы[ДатаОформления] <= _D
                && ( Заказы[ДатаОтгрузки] > _D || ISBLANK ( Заказы[ДатаОтгрузки] ) )
        )
    )

MAX(Календарь[Дата]) — текущая дата в контексте (последний день периода/строки). FILTER по ALL(Заказы) отбирает заказы, активные на эту дату. Положите меру на ось дат — получите кривую «сколько открыто в каждый день».

Сердце паттерна

Календарь не связан с обеими датами заказа физически — он служит «таймлайном», а мера через FILTER сама проверяет попадание интервала [начало; конец) на текущую дату. Так одна строка факта «раскрывается» на все свои дни.

Тонкости

  • Граница интервала. Решите, включать ли день отгрузки: > или >=. Для «открыт на конец дня» обычно ДатаОтгрузки > D.
  • Незавершённые. Пустая ДатаОтгрузки = ещё открыт; не забудьте ISBLANK.
  • Производительность. FILTER(ALL(Заказы)) на каждую дату — дорого на больших фактах. На проде оптимизируют: разворачивают заказы в «снимки по датам» в ETL, либо считают накопительно (открыто = оформлено − отгружено нарастающим итогом).

Накопительный приём (быстрее)

Эквивалент без перебора интервалов: открыто на дату = (оформлено всего до даты) − (отгружено всего до даты).

Открытые заказы (накопит.) =
VAR _D = MAX ( Календарь[Дата] )
VAR _Оформлено = CALCULATE ( COUNTROWS ( Заказы ), Заказы[ДатаОформления] <= _D )
VAR _Отгружено = CALCULATE ( COUNTROWS ( Заказы ), Заказы[ДатаОтгрузки] <= _D )
RETURN
    _Оформлено - _Отгружено

Часто это быстрее FILTER по интервалу и проще читается.

Не считайте «открытые» простым COUNTROWS

COUNTROWS(Заказы) в контексте даты даст заказы, у которых эта дата = дата оформления, а не все активные. «Открыто на дату» — это интервальная логика, а не принадлежность одной дате.

Что дальше

Интервальные события закрыли. Последний паттерн блока — конвертация валют. Следующий урок.

Как посчитать, сколько заказов открыто на каждую дату (есть дата оформления и дата отгрузки)?
«Открыт на дату» — это попадание интервала [оформление; отгрузка) на дату D, а не принадлежность одной дате. Считают через FILTER по интервалу или эквивалентно: оформлено до D минус отгружено до D.
Прогресс сохраняется в вашем браузере.
§ Power BI под ключ

Нужно внедрить
это в компании?

Соберём DWH, модель и дашборды под ваши данные. Бесплатная консультация — 30 минут.

Телефон+7 918 042 34 43