LATERAL allows the subquery on the right side of a JOIN to reference columns from rows on the left side. It is the JOIN version of a correlated subquery: “for each left-side row, run a subquery whose condition uses that row's values.”
SELECT * FROM categories c JOIN LATERAL ( -- The right-side subquery can reference c.category_id from the left SELECT * FROM products p WHERE p.category_id = c.category_id -- ← Reference c from the left (the core of LATERAL) ORDER BY p.revenue DESC LIMIT 3 -- Keep the top 3 per category ) sub ON TRUE; -- ON TRUE: the LATERAL subquery already handles the join condition
Using the categories and products tables, retrieve the 3 products with the highest revenue in each category, including the category name. Include every product for categories with fewer than 3 products (LEFT JOIN LATERAL). Sort the result by category ID and descending revenue.
| category_id | name |
|---|---|
| 1 | Drinks |
| 2 | Food |
| product_id | category_id | name | revenue |
|---|---|---|---|
| P01 | 1 | Coffee | 50000 |
| P02 | 1 | Tea | 30000 |
| P03 | 1 | Green tea | 45000 |
| P04 | 1 | Water | 20000 |
| P05 | 2 | Bread | 25000 |
| P06 | 2 | Rice ball | 18000 |
Top 3 products in each category (LEFT JOIN LATERAL):
| category_id | category_name | product_id | product_name | revenue | rank |
|---|---|---|---|---|---|
| 1 | Drinks | P01 | Coffee | 50000 | 1 |
| 1 | Drinks | P03 | Green tea | 45000 | 2 |
| 1 | Drinks | P02 | Tea | 30000 | 3 |
| 2 | Food | P05 | Bread | 25000 | 1 |
| 2 | Food | P06 | Rice ball | 18000 | 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
PostgreSQL's JSONB type stores JSON data in binary form and enables fast searches with indexes. It is useful when flexible data structures such as user settings, tags, and metadata need to be handled inside an RDB.
-- Extract JSON values (the text result can be cast) col ->> 'key' -- Extract as text (commonly used) col -> 'key' -- Keep the JSONB type (continue into nested references) col ->> 0 -- Extract JSON array index 0 as text -- Containment check (fast when combined with a GIN index) col @> '{"key": "value"}'::jsonb -- Check whether col contains the specified key/value -- Partial update of a JSONB column jsonb_set(col, '{key}', '"new_value"'::jsonb) -- Replace the value at the specified path
@> operator fast.For the settings JSONB column in user_settings, write (1) a query that extracts users whose notifications are enabled (notifications = true), and (2) an aggregate showing how many users use each theme (dark/light). In addition, (3) update the language for user_id=1 from 'en' to 'ja'.
| user_id | settings (JSONB) |
|---|---|
| 1 | {"theme":"dark","language":"en","notifications":true} |
| 2 | {"theme":"light","language":"ja","notifications":false} |
| 3 | {"theme":"dark","language":"ja","notifications":true} |
| 4 | {"theme":"light","language":"en","notifications":true} |
(1) Users with notifications enabled (notifications=true):
| user_id | notifications |
|---|---|
| 1 | true |
| 3 | true |
| 4 | true |
(2) User count by theme:
| theme | user_count |
|---|---|
| dark | 2 |
| light | 2 |
(3) After UPDATE (user_id=1):
| user_id | settings (after update) |
|---|---|
| 1 | {"theme":"dark","language":"ja","notifications":true} |
- 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
In a data-synchronization batch with an external system, updating only the differences is safer and faster than deleting every row and inserting everything again. This exercise uses subqueries such as NOT EXISTS to classify rows to add, update, or delete, then executes the changes in one transaction.
-- Present in new data but absent from current data → INSERT target WHERE NOT EXISTS ( SELECT 1 FROM current data c WHERE c.id = n.id ) -- Present in current data but absent from new data → DELETE target WHERE NOT EXISTS ( SELECT 1 FROM new data n WHERE n.id = c.id )
NOT IN can evaluate to FALSE or UNKNOWN for the whole predicate and produce unexpected results. In practice, NOT EXISTS is the safer choice.Compare products_current (the current database data) with products_new (the latest data fetched from an external system), then write a differential update batch that (1) INSERTs new products, (2) DELETEs products that are no longer needed, and (3) UPDATEs products whose values changed.
| product_id | name | price |
|---|---|---|
| P01 | Coffee | 300 |
| P02 | Tea | 250 |
| P03 | Green tea | 200 |
| product_id | name | price |
|---|---|---|
| P01 | Coffee | 350 |
| P02 | Tea | 250 |
| P04 | Roasted tea | 220 |
Final products_current table:
| product_id | name | price | Change |
|---|---|---|---|
| P01 | Coffee | 350 | UPDATE (price 300→350) |
| P02 | Tea | 250 | No change |
| P04 | Roasted tea | 220 | INSERT (new product) |
P03 (Green tea) does not exist in products_new, so it is DELETED.
- 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
EXPLAIN ANALYZE outputs a query's execution plan together with its actual execution time and row counts. It is a basic tool for identifying why a query is slow, such as an unused index or a scan of too many rows.
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 'U01'; -- Typical output: -- Seq Scan on orders (cost=0.00..12.50 rows=1 width=50) -- ↑ Full-table scan (index not used) -- Index Scan using idx_orders_user_id on orders -- ↑ Index used (fast)
| Output keyword | Meaning | Action |
|---|---|---|
| Seq Scan | Scan every row (index not used) | Create an index on the search column |
| Index Scan | Locate rows using an index | Good state |
| Hash Join | Join using a hash (suitable for large tables) | An index on the join key can sometimes change it to a Nested Loop |
| Estimated vs actual rows | Accuracy of statistics | Run ANALYZE to update table statistics when the values differ substantially |
EXPLAIN ANALYZE actually executes the query. When using EXPLAIN ANALYZE for UPDATE or DELETE, run it inside a transaction and then ROLLBACK.Here are slow queries. Use EXPLAIN to identify the problems and create appropriate indexes. Also show a case where a composite index is effective.
| Query | Problem |
|---|---|
| SELECT * FROM orders WHERE user_id = 'U01' | No INDEX on user_id → Seq Scan |
| SELECT * FROM orders WHERE user_id='U01' AND status='paid' ORDER BY ordered_at DESC | Multiple conditions → composite index not used |
| SELECT * FROM products WHERE LOWER(name) = 'coffee' | Function applied → index not used |
Actions for each problem (create an index and observe the EXPLAIN result change):
| Query | Before | After |
|---|---|---|
| WHERE user_id = 'U01' | Seq Scan (all rows) | Index Scan (fast) |
| WHERE user_id AND status ORDER BY ordered_at | Seq Scan or inefficient Index Scan | Index Scan (composite index fully used) |
| WHERE LOWER(name) = 'coffee' | Seq Scan (function disables the index) | Index Scan (functional index) |
- 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
ROLLUP is a GROUP BY extension that automatically adds hierarchical subtotals and a grand total. CUBE generates aggregates for every combination. GROUPING SETS is the generalized form that lets you specify the combinations flexibly. These features consolidate multiple GROUP BY queries into one batch-report query.
GROUP BY ROLLUP(col1, col2) -- Automatically generate col1+col2 detail, a col1 subtotal, and a grand total GROUP BY CUBE(col1, col2) -- Generate all 4 patterns: (col1,col2), (col1), (col2), () GROUP BY GROUPING SETS((col1, col2), (col1), ()) -- Enumerate any desired aggregate patterns (the generalization of ROLLUP/CUBE)
GROUPING(col1) returns 1 when the column is aggregated in a subtotal or total row. Use it to replace the NULL in a subtotal row with a label such as “Total.”Using ROLLUP, retrieve total sales by year, month, and category from the sales table. Include yearly totals and a grand total (subtotal rows), and use the GROUPING() function to replace NULL with readable labels. Also show an all-combinations aggregation using CUBE on the same table.
| sale_id | year | month | category | amount |
|---|---|---|---|---|
| 1 | 2024 | 1 | food | 3000 |
| 2 | 2024 | 1 | drink | 2000 |
| 3 | 2024 | 2 | food | 4000 |
| 4 | 2024 | 2 | drink | 1500 |
| 5 | 2023 | 12 | food | 5000 |
| 6 | 2023 | 12 | drink | 3000 |
ROLLUP (hierarchical aggregation: year → month → category):
| year_label | month_label | category_label | total_amount |
|---|---|---|---|
| 2023 | 12 | drink | 3000 |
| 2023 | 12 | food | 5000 |
| 2023 | December total | — | 8000 |
| 2023 total | — | — | 8000 |
| 2024 | 1 | drink | 2000 |
| 2024 | 1 | food | 3000 |
| 2024 | January total | — | 5000 |
| 2024 | 2 | drink | 1500 |
| 2024 | 2 | food | 4000 |
| 2024 | February total | — | 5500 |
| 2024 total | — | — | 10500 |
| Grand total | — | — | 18500 |
- 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