SQL Practice Platform
Article

Complete Guide to Window Functions in SQL – NTILE, SUM, AVG, COUNT, MIN, MAX with OVER() (From Noob to Pro)

Complete Guide to Window Functions in SQL – NTILE, SUM, AVG, COUNT, MIN, MAX with OVER() (From Noob to Pro)

Hey, I’m Gaurav.

After learning `RANK()`, `DENSE_RANK()`, `ROW_NUMBER()`, `LAG()`, and `LEAD()`, I thought I had mastered window functions.

Then the interviewer asked:  
"Divide customers into 4 equal groups based on their total spending."

My noob brain froze again.

I had no idea how to divide rows into buckets.

Today, I’m covering the remaining important window functions that complete the picture:

- `NTILE()` – Divide into equal groups
- Aggregate functions with `OVER()` (`SUM`, `AVG`, `COUNT`, `MIN`, `MAX`)

We’ll go from brute-force confusion to clear thinking, with live practice links.

All examples use our `orders` and `users` tables. Practice live here: [sqlpractice.in](https://sqlpractice.in)

---

1. NTILE() – Dividing Rows into Buckets

Question: Divide all customers into 4 equal groups (quartiles) based on their total spending.

Noob approach:
- Try to use `RANK()` and then manually divide (very messy)
- Write complex subqueries with `COUNT()` and math

Better thinking process:
1. I need to assign each customer to a group.
2. The groups should be as equal as possible.
3. Higher spending customers should go to higher groups.

`NTILE(n)` does exactly that — it divides the rows into `n` equal parts.

Correct Solution:
```sql
WITH customer_spending AS (
    SELECT 
        u.id,
        u.name,
        SUM(o.total_price) AS total_spent
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    GROUP BY u.id, u.name
)
SELECT 
    name,
    total_spent,
    NTILE(4) OVER (ORDER BY total_spent DESC) AS spending_quartile
FROM customer_spending
ORDER BY total_spent DESC;
```

Practice Question 1 (Medium)  
Divide customers into 5 equal groups based on total spending (highest to lowest).  
[Try it live →](https://sqlpractice.in/?question=ntile-basic)

Practice Question 2 (Hard)  
Show the top 25% (quartile 1) customers by spending.  
[Try it live →](https://sqlpractice.in/?question=ntile-top-quartile)

---

2. Aggregate Functions with OVER() (The Real Power)

You already know `SUM()`, `AVG()`, `COUNT()` with `GROUP BY`.

But when combined with `OVER()`, they become extremely powerful because they don’t collapse rows.

a) Running / Cumulative Aggregates

Example: Running total of sales per customer

```sql
SELECT 
    u.name,
    o.ordered_at,
    o.total_price,
    SUM(o.total_price) OVER (
        PARTITION BY u.id 
        ORDER BY o.ordered_at
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS running_total
FROM orders o
JOIN users u ON o.user_id = u.id;
```

Practice Question 3 (Easy)  
Show running total of sales for each customer.  
[Try it live →](https://sqlpractice.in/?question=running-total)

b) Moving Average / Rolling Average

Example: 3-order moving average of amount per customer

```sql
SELECT 
    u.name,
    o.total_price,
    AVG(o.total_price) OVER (
        PARTITION BY u.id 
        ORDER BY o.ordered_at
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg_3
FROM orders o
JOIN users u ON o.user_id = u.id;
```

Practice Question 4 (Hard)  
Calculate 3-order moving average for each customer.  
[Try it live →](https://sqlpractice.in/?question=moving-average)

c) Compare with Overall / Group Average

Example: Show each order amount and how it compares to the customer’s average order value.

```sql
SELECT 
    u.name,
    o.total_price,
    AVG(o.total_price) OVER (PARTITION BY u.id) AS customer_avg,
    o.total_price - AVG(o.total_price) OVER (PARTITION BY u.id) AS difference_from_avg
FROM orders o
JOIN users u ON o.user_id = u.id;
```

Practice Question 5 (Medium)  
Show each order and its difference from the customer’s average order value.  
[Try it live →](https://sqlpractice.in/?question=compare-with-average)

---

Complete Thinking Framework for Any Window Function Question

When you face any window function question, ask yourself these questions in order:

1. What do I need to calculate?  
   → Sum, Average, Count, Rank, Previous/Next, Group division?

2. Should it reset for every group?  
   → Use `PARTITION BY` (customer, department, month, etc.)

3. In what order should the calculation happen?  
   → Use `ORDER BY` (usually date, id, or value)

4. Over which rows? (Frame clause)  
   → `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW` → Running total  
   → `ROWS BETWEEN 2 PRECEDING AND CURRENT ROW` → Moving average  
   → No frame → Entire partition

5. Which function fits best?
   - Ranking → `RANK()`, `DENSE_RANK()`, `ROW_NUMBER()`
   - Previous/Next → `LAG()`, `LEAD()`
   - Divide into groups → `NTILE()`
   - Aggregates across rows → `SUM()`, `AVG()`, `COUNT()` with `OVER()`

---

Final Thoughts

Window functions were one of the hardest topics for me initially.  
The moment I stopped trying to memorize syntax and started asking:

- “What do I need to calculate?”
- “Should it reset per group?”
- “Over which rows?”

…everything became much clearer.

Window functions don’t just make queries faster — they make your thinking cleaner.

Go practice all these window function questions on [sqlpractice.in](https://sqlpractice.in) — especially the NTILE, moving average, and comparison ones.

You’ll feel the difference once you start thinking in “windows” instead of subqueries.

Keep going — you’re building real SQL confidence! 💪

Building sqlpractice.in to help everyone master SQL for interviews — completely free.