Bird
Raised Fist0
SQLquery~20 mins

Moving averages with window frames in SQL - Practice Problems & Coding Challenges

Choose your learning style10 modes available
Challenge - 5 Problems
🎖️
Master of Moving Averages
Get all challenges correct to earn this badge!
Test your skills under time pressure!
❓ query_result
intermediate
2:00remaining
Calculate 3-day moving average using ROWS frame
Given a sales table with columns sale_date and amount, what is the output of this query?

SELECT sale_date, amount, 
       AVG(amount) OVER (ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM sales
ORDER BY sale_date;
SQL
CREATE TABLE sales (sale_date DATE, amount INT);
INSERT INTO sales VALUES
('2024-01-01', 100),
('2024-01-02', 200),
('2024-01-03', 300),
('2024-01-04', 400),
('2024-01-05', 500);
A[{"sale_date": "2024-01-01", "amount": 100, "moving_avg": 100.0}, {"sale_date": "2024-01-02", "amount": 200, "moving_avg": 150.0}, {"sale_date": "2024-01-03", "amount": 300, "moving_avg": 200.0}, {"sale_date": "2024-01-04", "amount": 400, "moving_avg": 300.0}, {"sale_date": "2024-01-05", "amount": 500, "moving_avg": 400.0}]
B[{"sale_date": "2024-01-01", "amount": 100, "moving_avg": 100.0}, {"sale_date": "2024-01-02", "amount": 200, "moving_avg": 200.0}, {"sale_date": "2024-01-03", "amount": 300, "moving_avg": 300.0}, {"sale_date": "2024-01-04", "amount": 400, "moving_avg": 400.0}, {"sale_date": "2024-01-05", "amount": 500, "moving_avg": 500.0}]
C[{"sale_date": "2024-01-01", "amount": 100, "moving_avg": 100.0}, {"sale_date": "2024-01-02", "amount": 200, "moving_avg": 150.0}, {"sale_date": "2024-01-03", "amount": 300, "moving_avg": 300.0}, {"sale_date": "2024-01-04", "amount": 400, "moving_avg": 400.0}, {"sale_date": "2024-01-05", "amount": 500, "moving_avg": 500.0}]
D[{"sale_date": "2024-01-01", "amount": 100, "moving_avg": null}, {"sale_date": "2024-01-02", "amount": 200, "moving_avg": null}, {"sale_date": "2024-01-03", "amount": 300, "moving_avg": 200.0}, {"sale_date": "2024-01-04", "amount": 400, "moving_avg": 300.0}, {"sale_date": "2024-01-05", "amount": 500, "moving_avg": 400.0}]
Attempts:
2 left
💡 Hint
Think about how ROWS BETWEEN 2 PRECEDING AND CURRENT ROW includes the current row and two before it.
❓ query_result
intermediate
2:00remaining
Calculate 3-day moving average using RANGE frame
Given the same sales table, what is the output of this query?

SELECT sale_date, amount, 
       AVG(amount) OVER (ORDER BY sale_date RANGE BETWEEN INTERVAL '2' DAY PRECEDING AND CURRENT ROW) AS moving_avg
FROM sales
ORDER BY sale_date;
SQL
CREATE TABLE sales (sale_date DATE, amount INT);
INSERT INTO sales VALUES
('2024-01-01', 100),
('2024-01-02', 200),
('2024-01-03', 300),
('2024-01-04', 400),
('2024-01-05', 500);
A[{"sale_date": "2024-01-01", "amount": 100, "moving_avg": null}, {"sale_date": "2024-01-02", "amount": 200, "moving_avg": null}, {"sale_date": "2024-01-03", "amount": 300, "moving_avg": 200.0}, {"sale_date": "2024-01-04", "amount": 400, "moving_avg": 300.0}, {"sale_date": "2024-01-05", "amount": 500, "moving_avg": 400.0}]
B[{"sale_date": "2024-01-01", "amount": 100, "moving_avg": 150.0}, {"sale_date": "2024-01-02", "amount": 200, "moving_avg": 150.0}, {"sale_date": "2024-01-03", "amount": 300, "moving_avg": 300.0}, {"sale_date": "2024-01-04", "amount": 400, "moving_avg": 400.0}, {"sale_date": "2024-01-05", "amount": 500, "moving_avg": 500.0}]
C[{"sale_date": "2024-01-01", "amount": 100, "moving_avg": 100.0}, {"sale_date": "2024-01-02", "amount": 200, "moving_avg": 200.0}, {"sale_date": "2024-01-03", "amount": 300, "moving_avg": 300.0}, {"sale_date": "2024-01-04", "amount": 400, "moving_avg": 400.0}, {"sale_date": "2024-01-05", "amount": 500, "moving_avg": 500.0}]
D[{"sale_date": "2024-01-01", "amount": 100, "moving_avg": 100.0}, {"sale_date": "2024-01-02", "amount": 200, "moving_avg": 150.0}, {"sale_date": "2024-01-03", "amount": 300, "moving_avg": 200.0}, {"sale_date": "2024-01-04", "amount": 400, "moving_avg": 300.0}, {"sale_date": "2024-01-05", "amount": 500, "moving_avg": 400.0}]
Attempts:
2 left
💡 Hint
RANGE frame includes rows within 2 days before the current sale_date.
📝 Syntax
advanced
1:30remaining
Identify the syntax error in window frame clause
Which option contains a syntax error in the window frame clause of this query?

SELECT sale_date, amount, 
       AVG(amount) OVER (ORDER BY sale_date ROWS BETWEEN CURRENT ROW AND 2 PRECEDING) AS moving_avg
FROM sales;
AROWS BETWEEN 2 PRECEDING AND CURRENT ROW
BROWS BETWEEN CURRENT ROW AND 2 PRECEDING
CROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
DROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
Attempts:
2 left
💡 Hint
The frame must start with a lower boundary and end with a higher boundary.
❓ optimization
advanced
2:00remaining
Optimize moving average calculation for large dataset
You have a large sales table with millions of rows. Which approach is most efficient to calculate a 7-day moving average of amount ordered by sale_date?
AUse AVG(amount) OVER (ORDER BY sale_date RANGE BETWEEN INTERVAL '6' DAY PRECEDING AND CURRENT ROW)
BUse AVG(amount) OVER (ORDER BY sale_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
CUse a correlated subquery to calculate average for each row
DUse a self join on sale_date between current date and 6 days before and aggregate
Attempts:
2 left
💡 Hint
Consider how the data is distributed and the type of frame that matches date intervals.
🧠 Conceptual
expert
2:30remaining
Understanding difference between ROWS and RANGE frames
Which statement correctly explains the difference between ROWS and RANGE window frames in moving average calculations?
AROWS frame can only be used with numeric ORDER BY; RANGE frame only with date ORDER BY.
BROWS frame includes all rows with values within a specified range; RANGE frame counts physical rows before the current row.
CROWS frame counts physical rows before the current row; RANGE frame includes all rows with values within a specified range of the ORDER BY expression.
DROWS and RANGE frames behave identically when ORDER BY is on a numeric column.
Attempts:
2 left
💡 Hint
Think about how each frame defines the window based on position vs value.

Practice

(1/5)
1. What does the SQL clause ROWS BETWEEN 2 PRECEDING AND CURRENT ROW do in a window function?
easy
A. It calculates the function over the current row and the two rows before it.
B. It calculates the function over the current row and the next two rows.
C. It calculates the function over all rows in the table.
D. It calculates the function only on the current row.

Solution

  1. Step 1: Understand the window frame boundaries

    The clause ROWS BETWEEN 2 PRECEDING AND CURRENT ROW means the frame includes the current row and the two rows before it.
  2. Step 2: Apply to window function calculation

    The window function (like AVG) will compute its result using values from these three rows.
  3. Final Answer:

    It calculates the function over the current row and the two rows before it. -> Option A
  4. Quick Check:

    Window frame includes current + 2 preceding rows = C [OK]
Hint: Preceding means rows before current, including current [OK]
Common Mistakes:
  • Confusing PRECEDING with FOLLOWING
  • Thinking it includes future rows
  • Assuming it covers entire table
2. Which of the following is the correct syntax to calculate a 3-row moving average of the column sales ordered by date?
easy
A. SELECT date, AVG(sales) OVER (ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data;
B. SELECT date, AVG(sales) OVER (PARTITION BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data;
C. SELECT date, AVG(sales) OVER (ORDER BY date ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING) AS moving_avg FROM sales_data;
D. SELECT date, AVG(sales) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data;

Solution

  1. Step 1: Identify correct window function syntax

    The correct syntax requires OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) to define ordering and frame.
  2. 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.
  3. Final Answer:

    SELECT date, AVG(sales) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data; -> Option D
  4. Quick Check:

    ORDER BY + ROWS BETWEEN 2 PRECEDING AND CURRENT ROW = D [OK]
Hint: Use ORDER BY with ROWS BETWEEN for moving averages [OK]
Common Mistakes:
  • Using PARTITION BY instead of ORDER BY for moving average
  • Omitting ORDER BY clause
  • Using FOLLOWING instead of PRECEDING
3. Given the table 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 | 300
medium
A. [{"date": "2024-01-01", "amount": 100, "moving_avg": 150}, {"date": "2024-01-02", "amount": 200, "moving_avg": 200}, {"date": "2024-01-03", "amount": 300, "moving_avg": 300}]
B. [{"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}]
C. [{"date": "2024-01-01", "amount": 100, "moving_avg": 100}, {"date": "2024-01-02", "amount": 200, "moving_avg": 200}, {"date": "2024-01-03", "amount": 300, "moving_avg": 300}]
D. [{"date": "2024-01-01", "amount": 100, "moving_avg": null}, {"date": "2024-01-02", "amount": 200, "moving_avg": 150}, {"date": "2024-01-03", "amount": 300, "moving_avg": 250}]

Solution

  1. 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.
  2. 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.
  3. 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 B
  4. Quick Check:

    Moving average includes current + 1 preceding row = A [OK]
Hint: Average current and previous row amounts for moving average [OK]
Common Mistakes:
  • Including future rows instead of preceding
  • Calculating average without window frame
  • Assuming null for first row moving average
4. Identify the error in this SQL query for calculating a moving average:
SELECT date, amount, AVG(amount) OVER (ORDER BY date ROWS BETWEEN CURRENT ROW AND 2 PRECEDING) AS moving_avg FROM sales_data;
medium
A. AVG() cannot be used as a window function.
B. The ORDER BY clause is missing.
C. The window frame boundaries are in the wrong order; PRECEDING must come before CURRENT ROW.
D. The query is correct and will run without errors.

Solution

  1. Step 1: Check window frame syntax

    The frame clause ROWS BETWEEN CURRENT ROW AND 2 PRECEDING is invalid because the start boundary must be before the end boundary.
  2. Step 2: Correct the order of boundaries

    The correct syntax is ROWS BETWEEN 2 PRECEDING AND CURRENT ROW, with the smaller offset first.
  3. Final Answer:

    The window frame boundaries are in the wrong order; PRECEDING must come before CURRENT ROW. -> Option C
  4. Quick Check:

    Window frame start must be before end = A [OK]
Hint: Start frame boundary must be before end boundary [OK]
Common Mistakes:
  • Reversing PRECEDING and CURRENT ROW order
  • Thinking AVG() can't be window function
  • Ignoring window frame syntax rules
5. You want to calculate a 5-day moving average of daily sales, but only include days where sales were above 100. Which SQL query correctly applies this condition inside the moving average calculation?
hard
A. 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;
B. SELECT date, sales, AVG(sales) OVER (ORDER BY date ROWS BETWEEN 4 PRECEDING AND CURRENT ROW) WHERE sales > 100 FROM sales_data;
C. 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;
D. 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;

Solution

  1. 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.
  2. 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.
  3. 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 A
  4. Quick Check:

    Use CASE inside AVG to filter rows in window [OK]
Hint: Use CASE inside AVG to filter values in window frame [OK]
Common Mistakes:
  • Filtering rows outside window function
  • Using PARTITION BY for filtering condition
  • Assuming FILTER clause always works
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard