Moving averages help you see trends by smoothing out data over time. Window frames let you control which rows to include in the average.
Moving averages with window frames in SQL
SELECT column1, column2, AVG(target_column) OVER (ORDER BY order_column ROWS BETWEEN frame_start AND frame_end) AS moving_average FROM table_name;
ORDER BY inside OVER() defines the order of rows for the moving average.
ROWS BETWEEN defines the window frame, like how many rows before and after to include.
SELECT day, sales, AVG(sales) OVER (ORDER BY day ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_3_days FROM sales_data;
SELECT day, sales, AVG(sales) OVER (ORDER BY day ROWS BETWEEN 4 PRECEDING AND 1 PRECEDING) AS moving_avg_prev_4_days FROM sales_data;
SELECT day, sales, AVG(sales) OVER (ORDER BY day ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_avg FROM sales_data;
This example creates a simple sales table with 5 days of sales. It then calculates a 3-day moving average for each day, including the current day and the two days before.
CREATE TABLE sales_data ( day INT, sales INT ); INSERT INTO sales_data (day, sales) VALUES (1, 100), (2, 150), (3, 200), (4, 130), (5, 170); SELECT day, sales, AVG(sales) OVER (ORDER BY day ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_3_days FROM sales_data ORDER BY day;
If there are fewer rows than the frame size at the start, the average uses only available rows.
You can use RANGE instead of ROWS for value-based frames, but ROWS is simpler for beginners.
Always include ORDER BY inside the window function to define the sequence.
Moving averages smooth data by averaging values over a sliding window.
Window frames define which rows to include in the average relative to the current row.
Use ROWS BETWEEN with PRECEDING and CURRENT ROW to control the frame size.
Practice
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW do in a window function?Solution
Step 1: Understand the window frame boundaries
The clauseROWS BETWEEN 2 PRECEDING AND CURRENT ROWmeans the frame includes the current row and the two rows before it.Step 2: Apply to window function calculation
The window function (like AVG) will compute its result using values from these three rows.Final Answer:
It calculates the function over the current row and the two rows before it. -> Option AQuick Check:
Window frame includes current + 2 preceding rows = C [OK]
- Confusing PRECEDING with FOLLOWING
- Thinking it includes future rows
- Assuming it covers entire table
sales ordered by date?Solution
Step 1: Identify correct window function syntax
The correct syntax requiresOVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)to define ordering and frame.Step 2: Check each option
SELECT date, AVG(sales) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data; uses ORDER BY with correct frame. SELECT date, AVG(sales) OVER (PARTITION BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data; wrongly uses PARTITION BY instead of ORDER BY. SELECT date, AVG(sales) OVER (ORDER BY date ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING) AS moving_avg FROM sales_data; uses FOLLOWING which looks ahead, not behind. SELECT date, AVG(sales) OVER (ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data; misses ORDER BY clause.Final Answer:
SELECT date, AVG(sales) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data; -> Option DQuick Check:
ORDER BY + ROWS BETWEEN 2 PRECEDING AND CURRENT ROW = D [OK]
- Using PARTITION BY instead of ORDER BY for moving average
- Omitting ORDER BY clause
- Using FOLLOWING instead of PRECEDING
sales_data with columns date and amount, what is the output of this query?SELECT date, amount, AVG(amount) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data ORDER BY date;
Assume data:
date | amount
2024-01-01 | 100
2024-01-02 | 200
2024-01-03 | 300Solution
Step 1: Understand the window frame
The frame includes the current row and 1 preceding row, so for each row, average the amount of that row and the previous one.Step 2: Calculate moving averages row by row
Row 1 (2024-01-01): only itself, avg = 100.
Row 2 (2024-01-02): rows 1 and 2, avg = (100+200)/2 = 150.
Row 3 (2024-01-03): rows 2 and 3, avg = (200+300)/2 = 250.Final Answer:
[{"date": "2024-01-01", "amount": 100, "moving_avg": 100}, {"date": "2024-01-02", "amount": 200, "moving_avg": 150}, {"date": "2024-01-03", "amount": 300, "moving_avg": 250}] -> Option BQuick Check:
Moving average includes current + 1 preceding row = A [OK]
- Including future rows instead of preceding
- Calculating average without window frame
- Assuming null for first row moving average
SELECT date, amount, AVG(amount) OVER (ORDER BY date ROWS BETWEEN CURRENT ROW AND 2 PRECEDING) AS moving_avg FROM sales_data;
Solution
Step 1: Check window frame syntax
The frame clauseROWS BETWEEN CURRENT ROW AND 2 PRECEDINGis invalid because the start boundary must be before the end boundary.Step 2: Correct the order of boundaries
The correct syntax isROWS BETWEEN 2 PRECEDING AND CURRENT ROW, with the smaller offset first.Final Answer:
The window frame boundaries are in the wrong order; PRECEDING must come before CURRENT ROW. -> Option CQuick Check:
Window frame start must be before end = A [OK]
- Reversing PRECEDING and CURRENT ROW order
- Thinking AVG() can't be window function
- Ignoring window frame syntax rules
Solution
Step 1: Understand filtering inside window functions
To include only sales > 100 in the average, use a CASE expression inside AVG to nullify unwanted rows.Step 2: Evaluate each option
SELECT date, sales, AVG(CASE WHEN sales > 100 THEN sales ELSE NULL END) OVER (ORDER BY date ROWS BETWEEN 4 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data; uses CASE to include sales > 100, replacing others with NULL (ignored by AVG). SELECT date, sales, AVG(sales) OVER (ORDER BY date ROWS BETWEEN 4 PRECEDING AND CURRENT ROW) WHERE sales > 100 FROM sales_data; filters rows after window function (invalid syntax). SELECT date, sales, AVG(sales) FILTER (WHERE sales > 100) OVER (ORDER BY date ROWS BETWEEN 4 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data; uses FILTER clause which is not standard in all SQL versions. SELECT date, sales, AVG(sales) OVER (PARTITION BY sales > 100 ORDER BY date ROWS BETWEEN 4 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data; partitions by boolean condition, which changes grouping incorrectly.Final Answer:
SELECT date, sales, AVG(CASE WHEN sales > 100 THEN sales ELSE NULL END) OVER (ORDER BY date ROWS BETWEEN 4 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data; -> Option AQuick Check:
Use CASE inside AVG to filter rows in window [OK]
- Filtering rows outside window function
- Using PARTITION BY for filtering condition
- Assuming FILTER clause always works
Ready to prove it?
Pass the test and this pattern moves unknown
