Advanced SQL Proficient¶
🗄️ Databases · Level 4
When you'd use this
CTEs, window functions, subqueries, indexing and query optimization.
Write powerful queries — CTEs, window functions, smart indexing — and optimize slow ones using EXPLAIN.
Common Table Expressions (CTEs)¶
Name a subquery with WITH to make complex queries readable and reusable.
-- Named subquery — readable, reusable
WITH monthly_revenue AS (
SELECT
DATE_TRUNC('month', order_date) AS month,
SUM(amount) AS revenue
FROM orders
GROUP BY 1
),
monthly_growth AS (
SELECT
month,
revenue,
LAG(revenue) OVER (ORDER BY month) AS prev_month,
(revenue - LAG(revenue) OVER (ORDER BY month)) / LAG(revenue) OVER (ORDER BY month) * 100 AS growth_pct
FROM monthly_revenue
)
SELECT * FROM monthly_growth WHERE growth_pct > 10;
Window functions¶
Compute rankings, running totals, and moving averages without collapsing rows.
-- Ranking
SELECT
name,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS overall_rank,
DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank
FROM employees;
-- Running totals and moving averages
SELECT
date,
revenue,
SUM(revenue) OVER (ORDER BY date) AS running_total,
AVG(revenue) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma_7day
FROM daily_sales;
-- Lead/Lag (previous/next row)
SELECT
date,
price,
LAG(price, 1) OVER (ORDER BY date) AS yesterday_price,
price - LAG(price, 1) OVER (ORDER BY date) AS daily_change
FROM stock_prices;
-- Percentile
SELECT
name,
salary,
PERCENT_RANK() OVER (ORDER BY salary) AS percentile
FROM employees;
Subqueries vs JOINs¶
When a nested query is clearer vs when a join is faster.
-- Correlated subquery (runs per row — can be slow)
SELECT name, salary
FROM employees e
WHERE salary > (SELECT AVG(salary) FROM employees WHERE department = e.department);
-- Same as JOIN (usually faster)
SELECT e.name, e.salary
FROM employees e
JOIN (SELECT department, AVG(salary) as avg_sal FROM employees GROUP BY department) d
ON e.department = d.department
WHERE e.salary > d.avg_sal;
-- EXISTS (efficient for "has any match?" checks)
SELECT * FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id AND o.amount > 1000);
Indexing strategy¶
Add the right indexes (and column order) so queries use them.
-- B-tree index (default, most common)
CREATE INDEX idx_users_email ON users(email);
-- Composite index (multi-column — order matters!)
CREATE INDEX idx_orders_user_date ON orders(user_id, order_date DESC);
-- Supports: WHERE user_id = ? AND order_date > ?
-- Does NOT efficiently support: WHERE order_date > ? (without user_id)
-- Partial index (only index rows matching condition)
CREATE INDEX idx_active_users ON users(email) WHERE active = true;
-- EXPLAIN to check if index is used
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@example.com';
Query optimization tips¶
Read EXPLAIN, fix table scans, and avoid common slow patterns.
| Problem | Solution |
|---|---|
| Full table scan | Add appropriate index |
| N+1 queries (ORM) | Use JOIN or eager loading |
| Slow COUNT(*) | Use approximate counts or materialized views |
| Large IN clauses | Use temp table + JOIN |
| LIKE '%text%' | Full-text search index |
| Sorting large results | Index on ORDER BY column |
Practice Exercises¶
- Write a CTE that finds the top 3 customers by revenue per month.
- Use window functions to compute a 30-day moving average of daily sales.
- Optimize a slow query — use EXPLAIN, identify the bottleneck, add an index.
- Write a recursive CTE to traverse a hierarchical category tree.
- Compare correlated subquery vs JOIN performance on 1M rows.
💬 Discussion
Have a question about this topic? Found an error? Share your thoughts below.