SQL Topic
Advanced SQL Concepts
These concepts are used in analytics engineering, data warehousing, and large-scale production databases.
1. Common Table Expressions (CTEs)
A CTE is a temporary named result set used within a single query.
Basic CTE
WITH high_value_orders AS ( SELECT order_id, customer_id, total_amount FROM orders WHERE total_amount > 500 ) SELECT * FROM high_value_orders;
Why use CTEs?
-
Improve readability
-
Break complex queries into steps
-
Reuse intermediate logic
-
Recursively model hierarchies (recursive CTEs)
Recursive CTE example (organizational tree)
WITH RECURSIVE org AS ( SELECT emp_id, manager_id, name, 1 AS level FROM employees WHERE manager_id IS NULL UNION ALL SELECT e.emp_id, e.manager_id, e.name, o.level + 1 FROM employees e JOIN org o ON e.manager_id = o.emp_id ) SELECT * FROM org;
2. Window Functions
Window functions compute values across related rows, without collapsing groups.
Syntax:
<function>() OVER (PARTITION BY ... ORDER BY ...)
ROW_NUMBER()
SELECT customer_id, order_id, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date) AS rn FROM orders;
RANK()
Gives ties the same rank.
SELECT product_id, sales, RANK() OVER (ORDER BY sales DESC) AS sales_rank FROM product_sales;
Running total
SELECT order_date, amount, SUM(amount) OVER (ORDER BY order_date) AS running_total FROM sales;
7-day rolling window
SELECT order_date, SUM(amount) OVER ( ORDER BY order_date RANGE BETWEEN INTERVAL '7' DAY PRECEDING AND CURRENT ROW ) AS rolling_7_day_sales FROM sales;
3. Pivot / Unpivot Operations
Pivot (rows → columns)
Example: convert monthly sales into columns.
(PostgreSQL using FILTER)
SELECT product_id, SUM(amount) FILTER (WHERE month = 'Jan') AS jan_sales, SUM(amount) FILTER (WHERE month = 'Feb') AS feb_sales, SUM(amount) FILTER (WHERE month = 'Mar') AS mar_sales FROM monthly_sales GROUP BY product_id;
Unpivot (columns → rows)
SELECT product_id, month, amount FROM monthly_sales UNPIVOT ( amount FOR month IN (jan, feb, mar) );
(SQL Server/Oracle support UNPIVOT; MySQL/Postgres require UNION ALL.)
4. Handling Large Datasets
Techniques:
✔ LIMIT + OFFSET (for paging)
✔ Chunked updates (avoid locking)
✔ Indexing frequently filtered columns
✔ Avoid SELECT * on large tables
✔ Archive old data into history tables
Query pattern for large scans:
SELECT * FROM huge_table WHERE indexed_column > 10000 LIMIT 5000;
5. ACID Properties & Isolation Levels
ACID
| Property | Meaning |
|---|---|
| Atomicity | All or nothing |
| Consistency | Valid state → valid state |
| Isolation | Transactions don’t interfere |
| Durability | Data survives crashes |
Isolation Levels
| Level | Phenomena allowed |
|---|---|
| READ UNCOMMITTED | dirty reads |
| READ COMMITTED | non-repeatable reads |
| REPEATABLE READ | phantom reads |
| SERIALIZABLE | none (highest safety) |
Example:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Higher levels = more safety, but less concurrency.
6. Partitioned Tables
Used for huge tables (millions–billions of rows).
Benefits:
-
Faster queries
-
Faster deletes (drop partitions)
-
Improved vacuuming/maintenance
-
Better indexing at partition level
Example: partition by month (PostgreSQL)
CREATE TABLE sales ( id SERIAL, sale_date DATE, amount NUMERIC ) PARTITION BY RANGE (sale_date);
Then create partitions:
CREATE TABLE sales_2024_01 PARTITION OF sales FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
7. JSON Fields (PostgreSQL/MySQL)
Modern SQL supports JSON columns.
PostgreSQL example
SELECT data->>'first_name' AS first_name, data->'preferences'->>'theme' AS theme FROM users;
Indexing JSON (important for speed)
CREATE INDEX idx_user_preferences ON users USING GIN (data jsonb_path_ops);
MySQL Example
SELECT JSON_EXTRACT(profile, '$.hobbies[0]') AS first_hobby FROM users;
Practical Exercises
Exercise 1 — Use ROW_NUMBER to remove duplicates
Goal: Keep only the newest order per customer.
WITH ordered AS ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY customer_id ORDER BY order_date DESC ) AS rn FROM orders ) DELETE FROM orders WHERE order_id IN ( SELECT order_id FROM ordered WHERE rn > 1 );
Exercise 2 — Rolling 7-day sales report
SELECT order_date, SUM(amount) OVER ( ORDER BY order_date RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW ) AS rolling_7_day_sales FROM sales;
This gives for every day:
-
total sales for today + previous 6 days
Perfect for dashboards, forecasting, or trend monitoring.
Comments (0)
No comments yet. Be the first to comment!