Join us at FabCon Atlanta from March 16 - 20, 2026, for the ultimate Fabric, Power BI, AI and SQL community-led event. Save $200 with code FABCOMM.
Register now!View all the Fabric Data Days sessions on demand. View schedule
Understanding user behavior in learning systems, SaaS platforms, or product telemetry often starts with semi-additive calculations. These are the backbone of any time-series analysis, helping us compute “users as of the end of the month,” “active users this quarter,” or “retention rate.”
But semi-additive metrics only tell what happened at a point in time.
They do not reveal behavioral continuity.
They tell you what happened, not how consistently it happened.
For example, semi-additive modeling cannot answer questions such as:
These questions highlight critical dependencies between data points, which include:
To address these questions, a solution requires sequence analysis, specifically using run-length encoding through DAX or SQL window functions.
Implementing Continuous-Day Logic (DAX)
Assume you have a table: user_dailyactivitydate
Columns: userid, ActivityDate
Step 1: Here, the FILTER() creates a temporary table containing only the rows for the same user where the activity date is before the current row’s ActivityDate (using EARLIER to refer to the outer row).
CALCULATE(MAX(...)) then finds the latest ActivityDate from that filtered table, giving you the previous activity date.
prev_activity_date =
CALCULATE (
MAX ( user_dailyactivitydate[ActivityDate] ),
FILTER (
user_dailyactivitydate,
user_dailyactivitydate[userid] = EARLIER ( user_dailyactivitydate[userid] ) &&
user_dailyactivitydate[ActivityDate] < EARLIER ( user_dailyactivitydate[ActivityDate] )
)
)
Step 2: Identify whether the current date continues the sequence
is_continuous =
IF (
user_dailyactivitydate[ActivityDate] - user_dailyactivitydate[prev_activity_date] = 1,
1,
0
)
It checks whether today’s activity date is exactly 1 day after the previous activity date.
If yes → it marks the row as 1 (continuous streak)
If no → it marks it as 0 (streak broken)
Step 3: Assign run groups
run_group =
CALCULATE(
COUNTROWS(user_dailyactivitydate),
FILTER(
user_dailyactivitydate,
user_dailyactivitydate[userid] = EARLIER(user_dailyactivitydate[userid]) &&
user_dailyactivitydate[ActivityDate] <= EARLIER(user_dailyactivitydate[ActivityDate]) &&
user_dailyactivitydate[is_continuous] = 0
)
)
run_group computes a sequential segment identifier by counting streak-breaking rows for each user up to the current row, enabling DAX to cluster activity into discrete consecutive-day streaks.
Step 4: Compute the length of each run
consecutive_days =
CALCULATE(
COUNTROWS(user_dailyactivitydate),
ALLEXCEPT(user_dailyactivitydate, user_dailyactivitydate[userid], user_dailyactivitydate[run_group])
)
This column computes the length of each streak by counting the number of rows that belong to the same user and the same streak segment (run_group).
CALCULATE evaluates COUNTROWS() under a modified filter context. ALLEXCEPT removes all filters from ActivityLog, only keeping the filters on the userid and run_group
Once we have calculated consecutive_days for each run group, we can determine the user’s ongoing streak, the length of the current continuous engagement segment.
Current Streak =
VAR LastActivityDate =
MAX ( user_dailyactivitydate[ActivityDate] )
VAR LastRunGroup =
CALCULATE (
MAX ( user_dailyactivitydate[run_group] ),
user_dailyactivitydate[ActivityDate] = LastActivityDate
)
RETURN
CALCULATE (
MAX ( user_dailyactivitydate[consecutive_days] ),
user_dailyactivitydate[run_group] = LastRunGroup
)
The Current Streak measure derives the user’s active streak by anchoring the calculation on their most recent activity date. It first identifies the latest ActivityDate, then resolves the corresponding run_group that represents the user’s current streak segment. Using that run_group as the filter context, it returns the maximum consecutive_days value, which is the length of the user’s ongoing streak. This approach ensures the measure is dynamic, context-aware, and always reflects the user’s latest contiguous activity window.
Now you have a continuous engagement metric that behaves differently from standard time intelligence calculations.
When combined with semi-additive metrics, consecutive-day logic closes the behavioral analytics gap.
This combination of snapshot-based and sequence-based metrics gives you a richer, more predictive, and more actionable analytical foundation.
Download the sample file used in this blog from here.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.