Time-Series SQL
The patterns behind every trend chart
Time-series SQL covers date bucketing, period-over-period changes, running totals and comparisons — the patterns behind almost every line chart in BI.
What you will learn
- Bucket events into fixed periods with date functions
- Compute period-over-period and running totals
- Handle seasonality and calendar gaps honestly
What is this?
Time-series SQL shapes events over time: truncating timestamps into days/weeks/months, computing changes vs the prior period, running totals and year-over-year comparisons. Correct calendar handling (which week, which timezone, how to treat missing periods) distinguishes a trustworthy trend from a misleading one.
Why do you need it?
- Most BI dashboards are time series — their correctness is arguably the most important.
- Period-over-period comparisons silently mislead without a defined 'previous' period.
- Calendar gaps (no weekend data) can break naive running totals.
- Seasonality means a month-over-month jump may be noise; SQL lets you compute YoY.
Mental model
A tide table
Time-series bucketing
You read high tides by the hour but plan by the week — the same data, bucketed to the decision's horizon. Bucketing choices (hour/day/week) exist to match the decision, not the raw data.
How it works
- DATE_TRUNC/CAST normalize timestamps into period buckets.
- GROUPS/partitions compute per-period aggregates at the chosen cadence.
- LAG over an ordered partition yields the prior period's value for comparisons.
- Running totals use SUM(...) OVER (ORDER BY period).
- Joins to a date dimension expose gaps (days with zero data) so charts stay honest.
From events to a chronological report.
Examples
Monthly revenue with MoM and YoY
WITH monthly AS ( SELECT DATE_TRUNC('month', ts) AS month, SUM(amount) AS revenue FROM orders WHERE ts >= '2025-01-01' GROUP BY 1)SELECT month, revenue, LAG(revenue) OVER (ORDER BY month) AS prev_month, revenue * 1.0 / NULLIF(LAG(revenue) OVER (ORDER BY month), 0) - 1 AS mom, LAG(revenue, 12) OVER (ORDER BY month) AS prev_year, revenue * 1.0 / NULLIF(LAG(revenue, 12) OVER (ORDER BY month), 0) - 1 AS yoyFROM monthly ORDER BY month;LAG(offset) reaches back one month or twelve, giving MoM and YoY deltas from the same ordered partition. NULLIF avoids divide-by-zero on a missing prior period.
In production
- A subscription team charts MRR with MoM change bands and a 12-month YoY line to separate growth from seasonality.
- A retail weekly report buckets sales by ISO week using a calendar dimension index.
- An ops SLA report tracks rolling-7-day on-time % with a running window over the last N days.
Best practices
- Bucketing in the report query — but keep a calendar dimension to fill gaps.
- Define 'prior period' explicitly (last full calendar month, not last 30 days).
- Use a consistent timezone for all event timestamps before bucketing.
- For trends, show both value and its YoY; a green MoM spike in January may be seasonal.
Common mistakes
- Bucketing in the BI tool inconsistently with the SQL (week starts differ).
- Comparing an incomplete current month to a full prior month.
- Forgetting daylight-saving/gap days in daily aggregates.
- Using 'last 30 days' where calendar periods are the decision's frame.
Common issues
Monday totals appear half-emptyWeek bucketing starts Sunday, splitting the workweek
Force the week start via ISO week semantics or your date dimension.
MoM spike every JanuarySeasonality, not real growth
Add the YoY comparison to keep the report honest.
Debugging tips
- If weekly totals seem low, check which day starts the week in your bucketing.
- If YoY is empty, verify the dates cover 12 months of history.
- If a running total jumps, look for a gap or duplicated date in the order by key.
Interview checkpoint
Hands-on lab
- Bucket events into days and months
- Compute MoM and running total
- Handle a weekend gap honestly
Measure Revenue Trends in the Warehouse
40 min · intermediate
Quick quiz
Which SQL function pattern computes a period-over-period change?
Comparing a partial current month to a full previous month gives a fair MoM.