The full answer
Starfire needs to be careful when calculating rolling averages on time series data with gaps. Standard window functions in SQL can produce misleading results if gaps aren't handled correctly. Here's how she can address this in Databricks SQL:
1. Using ROWS BETWEEN for Fixed-Size Windows (Simplest, but Doesn't Handle Gaps Well):
If Starfire doesn't need to account for the gaps (i.e., she wants a rolling average over the last n rows regardless of the time difference between them), she can use a standard window function:
SELECT
timestamp_column,
value_column,
AVG(value_column) OVER (ORDER BY timestamp_column ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS rolling_avg_3_rows
FROM
your_delta_table;
However, as stated, this approach is not suitable for accurate time-based rolling averages with gaps.
2. Using RANGE BETWEEN for Time-Based Windows (Better, but Still Has Limitations):
RANGE BETWEEN allows specifying a time range for the window. However, with gaps, it can still lead to incorrect averages if the gaps are large.
SELECT
timestamp_column,
value_column,
AVG(value_column) OVER (ORDER BY timestamp_column RANGE BETWEEN INTERVAL 2 DAY PRECEDING AND CURRENT ROW) AS rolling_avg_2_days
FROM
your_delta_table;
This will include all rows within the last two days. If there's a large gap, it will average over a longer effective period.
3. Generating a Complete Time Series and Using LEFT JOIN (Most Accurate):
This is the most robust way to handle gaps.
- Generate a Time Series: Create a table or CTE (Common Table Expression) containing all the timestamps within the desired range.
WITH time_series AS (
SELECT explode(sequence(min(timestamp_column), max(timestamp_column), interval 1 day)) AS timestamp_day
FROM your_delta_table
)
SELECT ts.timestamp_day, COALESCE(yt.value_column, 0) as value_column
FROM time_series ts
LEFT JOIN your_delta_table yt ON date(ts.timestamp_day) = date(yt.timestamp_column)
ORDER BY ts.timestamp_day;
This generates a time series with daily intervals. Adjust the interval as needed (e.g., interval 1 hour, interval 1 minute). The COALESCE function handles missing values by filling them with 0 (or any other appropriate value).
- Calculate Rolling Average: Now that you have a complete time series, you can use a window function:
WITH time_series AS (
SELECT explode(sequence(min(timestamp_column), max(timestamp_column), interval 1 day)) AS timestamp_day
FROM your_delta_table
),
filled_data AS (
SELECT ts.timestamp_day, COALESCE(yt.value_column, 0) as value_column
FROM time_series ts
LEFT JOIN your_delta_table yt ON date(ts.timestamp_day) = date(yt.timestamp_column)
ORDER BY ts.timestamp_day
)
SELECT
timestamp_day,
value_column,
AVG(value_column) OVER (ORDER BY timestamp_day RANGE BETWEEN INTERVAL 2 DAY PRECEDING AND CURRENT ROW) AS rolling_avg_2_days
FROM filled_data;
4. Using fill() with RANGE BETWEEN (Alternative for Simpler Cases):
If the gaps are not too large and you want to fill the missing values with the previous available value, you can use the fill() function.
SELECT
timestamp_column,
value_column,
LAST(value_column, true) OVER (ORDER BY timestamp_column RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as filled_value,
AVG(value_column) OVER (ORDER BY timestamp_column RANGE BETWEEN INTERVAL 2 DAY PRECEDING AND CURRENT ROW) AS rolling_avg_2_days
FROM your_delta_table;
This method is simpler than generating the whole time series but might not be appropriate if you need to handle gaps in a different way (e.g., filling with 0).
Recommendation:
For accurate rolling averages on time series data with gaps, generating a complete time series using sequence() and LEFT JOIN (method 3) is the most reliable approach. This ensures that the rolling average is calculated over the correct time period, regardless of missing data points. The fill() function can be a good alternative if you simply want to fill the gaps with the last known value and the gaps are not too significant.
Last updated 17 January 2025