A correlated subquery references a column from the outer query and is evaluated for each outer row, making it useful for finding a maximum or minimum under the same condition as the current row.
SELECT * FROM orders o1 WHERE o1.ordered_at = ( SELECT MAX(o2.ordered_at) -- The inner query references the outer o1.user_id → correlated subquery FROM orders o2 WHERE o2.user_id = o1.user_id -- ← This is the core of the correlation: evaluated for each outer row );
ROW_NUMBER() window-function version may be faster for large data sets. It remains a readable choice for small data sets and straightforward requirements.Retrieving the latest row in a group—such as each user’s latest login or each product’s last price change—is one of the most common API and batch-processing patterns.
From the orders table, retrieve the row for each user (user_id) with the maximum ordered_at. If a user has multiple orders, return only the latest row. Sort the result by user_id ascending.
| order_id | user_id | amount | ordered_at |
|---|---|---|---|
| 1001 | U01 | 3200 | 2024-03-10 |
| 1002 | U01 | 1500 | 2024-05-20 |
| 1003 | U02 | 8000 | 2024-04-01 |
| 1004 | U02 | 2100 | 2024-06-15 |
| 1005 | U03 | 500 | 2024-02-28 |
| 1006 | U01 | 4700 | 2024-06-01 |
One latest order per user (user_id ascending):
| order_id | user_id | amount | ordered_at |
|---|---|---|---|
| 1006 | U01 | 4700 | 2024-06-01 |
| 1004 | U02 | 2100 | 2024-06-15 |
| 1005 | U03 | 500 | 2024-02-28 |
- Read every model answer, explanation and table-transition visualization across all 48 advanced sets (240 questions)
- One-time purchase — no subscription. Future advanced sets are included
- Background, problem and expected output stay free
A WITH clause (CTE, or Common Table Expression) defines a temporary named result set inside a query. Instead of nesting complex subqueries, you can separate the work into step-by-step blocks, greatly improving readability and maintainability.
WITH cte_name1 AS ( -- Block 1: define the first temporary table SELECT ... FROM some_table ), cte_name2 AS ( -- Block 2: cte_name1 is available here SELECT ... FROM cte_name1 ) SELECT * FROM cte_name2; -- The final SELECT produces the actual output
MATERIALIZEDthe MATERIALIZED option can force one computation), and 3) when writing recursive queries (see Q5).From the sales table, retrieve sales totals by month and product category, plus each category’s percentage of the monthly total. Use CTEs to build it in three stages: “monthly category aggregation” → “monthly total” → “ratio calculation”. Sort by month and category ascending.
| sale_id | category | amount | sold_at |
|---|---|---|---|
| 1 | food | 3000 | 2024-05-03 |
| 2 | drink | 1500 | 2024-05-10 |
| 3 | food | 2000 | 2024-05-20 |
| 4 | drink | 2500 | 2024-06-05 |
| 5 | food | 4000 | 2024-06-12 |
| 6 | drink | 1000 | 2024-06-20 |
Monthly/category sales and percentage of the monthly total:
| month | category | cat_total | month_total | ratio_pct |
|---|---|---|---|---|
| 2024-05 | drink | 1500 | 6500 | 23.08 |
| 2024-05 | food | 5000 | 6500 | 76.92 |
| 2024-06 | drink | 3500 | 7500 | 46.67 |
| 2024-06 | food | 4000 | 7500 | 53.33 |
- Read every model answer, explanation and table-transition visualization across all 48 advanced sets (240 questions)
- One-time purchase — no subscription. Future advanced sets are included
- Background, problem and expected output stay free
CASE WHEN is SQL’s conditional expression. Embedding it in an aggregate function enables conditional aggregation (pivot aggregation), where only rows meeting a condition are aggregated. PostgreSQL also supports the dedicated FILTER (WHERE ...) syntax.
-- Conditional aggregation with CASE WHEN (standard SQL) SUM(CASE WHEN status = 'active' THEN amount ELSE 0 END) AS active_sum -- Conditional aggregation with FILTER (PostgreSQL) SUM(amount) FILTER (WHERE status = 'active') AS active_sum
From the inventory table, retrieve the count of in-stock items (quantity > 0), out-of-stock items (quantity = 0), and all items on one row per warehouse as a pivot aggregation.
| item_id | warehouse | quantity |
|---|---|---|
| A01 | Tokyo | 50 |
| A02 | Tokyo | 0 |
| A03 | Tokyo | 20 |
| B01 | Osaka | 0 |
| B02 | Osaka | 0 |
| B03 | Osaka | 15 |
| C01 | Fukuoka | 100 |
| C02 | Fukuoka | 30 |
Inventory status summary by warehouse:
| warehouse | in_stock | out_of_stock | total_items |
|---|---|---|---|
| Tokyo | 2 | 1 | 3 |
| Osaka | 1 | 2 | 3 |
| Fukuoka | 2 | 0 | 2 |
- Read every model answer, explanation and table-transition visualization across all 48 advanced sets (240 questions)
- One-time purchase — no subscription. Future advanced sets are included
- Background, problem and expected output stay free
LAG(column, n) is a window function that returns the value n rows before the current row. LEAD(column, n) returns the value n rows after it. Its key feature is adding the difference from a previous row to the same row without aggregating rows with GROUP BY.
LAG(col, 1, 0) OVER ( -- Value one row before col; default 0 when there is no previous row PARTITION BY group_col -- Keep an independent order within each group ORDER BY sort_col -- Define “previous” and “next” by this order (required) ) LEAD(col, 1) OVER ( -- Value one row after col (the next row) ORDER BY sort_col )
ORDER BY inside the OVER clause Without a defined order, “the previous row” is undefined.monthly_sales table, retrieve monthly sales, previous-month sales, and month-over-month change (%) for each product (product_id). When there is no previous-month data (the first month), previous-month sales and month-over-month change may be NULL. Sort by product_id and month ascending.
| product_id | month | revenue |
|---|---|---|
| P01 | 2024-03 | 10000 |
| P01 | 2024-04 | 12000 |
| P01 | 2024-05 | 9000 |
| P02 | 2024-03 | 5000 |
| P02 | 2024-04 | 7500 |
| P02 | 2024-05 | 7500 |
Monthly sales with month-over-month change (product_id and month ascending):
| product_id | month | revenue | prev_revenue | mom_pct |
|---|---|---|---|---|
| P01 | 2024-03 | 10000 | NULL | NULL |
| P01 | 2024-04 | 12000 | 10000 | +20.00 |
| P01 | 2024-05 | 9000 | 12000 | -25.00 |
| P02 | 2024-03 | 5000 | NULL | NULL |
| P02 | 2024-04 | 7500 | 5000 | +50.00 |
| P02 | 2024-05 | 7500 | 7500 | 0.00 |
- Read every model answer, explanation and table-transition visualization across all 48 advanced sets (240 questions)
- One-time purchase — no subscription. Future advanced sets are included
- Background, problem and expected output stay free
WITH RECURSIVE is syntax for writing a recursive query in which a CTE references itself. It is used to traverse hierarchical data such as category trees, organization charts, and bills of materials.
WITH RECURSIVE cte AS ( -- ① Anchor member (the recursion starting point; runs once at the beginning) SELECT id, parent_id, name, 0 AS depth FROM categories WHERE id = 1 -- Select one starting row UNION ALL -- Connect the anchor and recursive members (fast because duplicates are not removed) -- ② Recursive member (cte references the previous iteration; repeat until it returns zero rows) SELECT c.id, c.parent_id, c.name, r.depth + 1 FROM categories c JOIN cte r ON c.parent_id = r.id -- Retrieve rows whose parent ID is the previous iteration’s id ) SELECT * FROM cte;
depth < 10 or loop detection using a path column.categories is a self-referencing hierarchy through parent_id. Retrieve all descendant categories starting from category_id = 1, including grandchildren and great-grandchildren, not just direct children with their depth (hierarchy level). Treat the starting point as depth 0.
| category_id | parent_id | name |
|---|---|---|
| 1 | NULL | Food |
| 2 | 1 | Drinks |
| 3 | 1 | Snacks |
| 4 | 2 | Coffee |
| 5 | 2 | Juice |
| 6 | 3 | Chocolate |
| 7 | 10 | Miscellaneous (separate tree) |
All descendants under category_id=1 (Food):
| category_id | parent_id | name | depth |
|---|---|---|---|
| 1 | NULL | Food | 0 |
| 2 | 1 | Drinks | 1 |
| 3 | 1 | Snacks | 1 |
| 4 | 2 | Coffee | 2 |
| 5 | 2 | Juice | 2 |
| 6 | 3 | Chocolate | 2 |
- Read every model answer, explanation and table-transition visualization across all 48 advanced sets (240 questions)
- One-time purchase — no subscription. Future advanced sets are included
- Background, problem and expected output stay free