runtime
43 / 88

lesson 43 Window functions · advanced

Rolling averages & missing dates

Calculate rolling trends while accounting for missing dates.

the idea

A rolling average smooths variation. ROWS counts rows, not calendar days. When dates are missing, a three-row average differs from a three-day average. Fill missing days with zero only if the business definition says absence means no activity.

tables in this lesson

The challenges use this data. Each column shows its type underneath; → marks a link to another table.

daily_metrics2 columns · 5 rows
daytext · keywatch_minutesinteger
2025-01-0130
2025-01-0260
2025-01-0490
2025-01-05120
2025-01-0760

all 5 rows

example

The idea in one query. Press Run to see what it returns; running examples never affects your progress.

example.sql
SELECT
  day,
  watch_minutes,
  ROUND(AVG(watch_minutes) OVER( ORDER BY day ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ),2) AS rolling_avg
FROM daily_metrics;

Watch for: Three rows are not three days when dates are missing.

build it step by step

Each step changes one thing. Run them in order and watch the result change.

  1. Notice missing dates. Jan 3 and Jan 6 are absent. A row frame skips those dates.

    step-1.sql
    SELECT *
    FROM daily_metrics
    ORDER BY day;
  2. Average observed rows. For Jan 4, the frame is Jan 1, Jan 2, Jan 4. That is three rows, not three calendar days.

    step-2.sql
    SELECT
      day,
      watch_minutes,
      ROUND(AVG(watch_minutes) OVER(ORDER BY day ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),2) AS rolling_avg
    FROM daily_metrics
    ORDER BY day
  3. Build a complete calendar. Missing days become zero under the explicit business rule.

    step-3.sql
    WITH RECURSIVE days(day) AS(
      SELECT '2025-01-01'
      UNION ALL
      SELECT date(day,'+1 day')
      FROM days
      WHERE day<'2025-01-07'
    )
    SELECT d.day,COALESCE(m.watch_minutes,0) AS watch_minutes
    FROM days d
    LEFT JOIN daily_metrics m ON m.day=d.day
    ORDER BY d.day;
  4. Average calendar days. Now each row is one day. The frame’s three rows are also three days.

    step-4.sql
    WITH RECURSIVE days(day) AS(
      SELECT '2025-01-01'
      UNION ALL
      SELECT date(day,'+1 day')
      FROM days
      WHERE day<'2025-01-07'
    ), complete AS(
      SELECT d.day,COALESCE(m.watch_minutes,0) AS watch_minutes
      FROM days d
      LEFT JOIN daily_metrics m ON m.day=d.day
    )
    SELECT
      day,
      watch_minutes,
      ROUND(AVG(watch_minutes) OVER(ORDER BY day ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),2) AS rolling_avg
    FROM complete
    ORDER BY day

challenge 1

Return day, watch_minutes and rolling_avg (2 decimals) for the current and previous 2 observed rows in daily_metrics, sorted by day. This task intentionally uses rows, not calendar days.

Return daywatch_minutesrolling_avgrows in the order asked

challenge-1.sql
 
To master this lesson, solve both challenges without hints and pass the concept check. 3 of 3 to go.