These functions pad strings to a fixed length and concatenate strings safely when NULL values are present.
LPAD('5', 6, '0') -- → '000005' Pad on the left with '0' to six characters LPAD('ABCDEFGH', 6, '0') -- → 'ABCDEF' Truncate the right side when input exceeds the length RPAD('FOOD', 8, '.') -- → 'FOOD....' Pad on the right with '.' to eight characters UPPER('tokyo') -- → 'TOKYO' Convert all letters to uppercase CONCAT_WS('-', '2024', 'TKY', '000005') -- → '2024-TKY-000005' Join with a separator CONCAT_WS('-', '2024', NULL, '000005') -- → '2024-000005' Skip NULL ✓ CONCAT('2024', '-', NULL, '-', '000005') -- → '2024--000005' Ignore NULL but duplicate separators ✗
CONCAT_WS(separator, a, b, c) automatically skips NULL arguments and inserts the separator only between the remaining values. PostgreSQL's regular CONCAT also ignores NULL, but passing separators as separate arguments can produce duplicates such as '2024--000005'. Real-world data often contains NULL values, so prefer CONCAT_WS for separator-delimited concatenation.Generate the following columns from the invoices table.
① padded_seq: a six-digit, zero-padded sequence code from the integer seq_no
② invoice_no: a report number in the form {fiscal_year}-{UPPER(branch_code)}-{padded_seq}
③ display_label: customer_name padded on the right with dots to 16 characters.
| invoice_id | fiscal_year | branch_code | seq_no | customer_name |
|---|---|---|---|---|
| 1 | 2024 | tky | 5 | Alpine Cafe |
| 2 | 2024 | osk | 123 | Tech Corp |
| 3 | 2024 | tky | 42 | Garden Shop |
| 4 | 2025 | ngy | 1 | Book World |
Expected output (generated report codes):
| invoice_id | padded_seq | invoice_no | display_label |
|---|---|---|---|
| 1 | 000005 | 2024-TKY-000005 | Alpine Cafe..... |
| 2 | 000123 | 2024-OSK-000123 | Tech Corp....... |
| 3 | 000042 | 2024-TKY-000042 | Garden Shop..... |
| 4 | 000001 | 2025-NGY-000001 | Book World...... |
display_label has a fixed length of 16 characters (Alpine Cafe has 11 characters, so five dots are added).
- 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
These functions calculate intervals relative to the current date and time.
CURRENT_DATE -- → '2024-12-15' Today's date (DATE) NOW() -- → '2024-12-15 09:30+09' Current date and time (TIMESTAMP WITH TIME ZONE) -- Subtract DATE values → INTEGER (number of days) '2025-01-14'::DATE - '2024-12-15'::DATE -- → 30 (integer total days) -- AGE → INTERVAL (human-readable relative representation) AGE('2025-11-30'::DATE, '2024-12-15'::DATE) -- → '11 mons 15 days' (not a total of 350 days!) EXTRACT(DAY FROM AGE(...)) -- → 15 ← day component only, not total days -- Add or subtract INTERVAL to calculate future or past dates '2025-01-14'::DATE + INTERVAL '1 year' -- → '2026-01-14 00:00:00' NOW() - INTERVAL '30 days' -- → timestamp 30 days ago
AGE('2025-11-30', '2024-12-15') returns the INTERVAL '11 mons 15 days'. Applying EXTRACT(DAY FROM ...) returns 15, the day component only, not the total of 350 days. Use expires_at::DATE - CURRENT_DATE when you need total days as an integer.Using CURRENT_DATE as the reference, calculate the following from the subscriptions table (the example output uses 2024-12-15).
① days_remaining: days until expiration, negative if already expired
② status: one of 'Expired', 'Expiring Soon' (within 30 days), or 'Active'
③ renewal_at: the next scheduled renewal date (current expiration date + one year) in 'YYYY-MM-DD' format.
Sort the result by days_remaining ascending.
| user_id | plan | started_at | expires_at |
|---|---|---|---|
| 1 | basic | 2024-01-15 | 2025-01-14 |
| 2 | pro | 2023-06-01 | 2024-07-31 |
| 3 | premium | 2024-11-01 | 2025-11-30 |
| 4 | basic | 2024-03-01 | 2024-12-31 |
Expected output (CURRENT_DATE = 2024-12-15, days_remaining ascending):
| user_id | plan | days_remaining | status | renewal_at |
|---|---|---|---|---|
| 2 | pro | -137 | Expired | 2025-07-31 |
| 4 | basic | 16 | Expiring Soon | 2025-12-31 |
| 1 | basic | 30 | Expiring Soon | 2026-01-14 |
| 3 | premium | 350 | Active | 2026-11-30 |
user_id=2 expired 137 days before 2024-12-15. user_id=1 and 4 expire within 30 days.
- 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
This pattern combines multi-condition branching, lower and upper bounds, and a CTE (Common Table Expression).
-- Simple CASE form for equality: CASE value WHEN comparison THEN result CASE customer_rank WHEN 'gold' THEN 0.10 -- When customer_rank = 'gold' WHEN 'silver' THEN 0.05 ELSE 0.00 -- When nothing matches; make ELSE explicit END GREATEST(405, 500) -- → 500 Maximum argument, used as a lower bound GREATEST(NULL, 500) -- → 500 NULL is ignored in PostgreSQL LEAST(12000, 10000) -- → 10000 Minimum argument, used as an upper bound
Calculate prices from the orders table after applying a tiered discount based on customer rank.
Discount rates: platinum=20% / gold=10% / silver=5% / bronze=0%
① discount_rate: the rate for the customer's rank
② discounted: the discounted amount, rounded to an integer with ROUND
③ final_price: the final price clamped to a minimum of 500.
Use a CTE (WITH clause) to avoid repeating the CASE WHEN expression.
| order_id | customer_id | customer_rank | subtotal |
|---|---|---|---|
| 1 | 101 | platinum | 15000 |
| 2 | 102 | gold | 8500 |
| 3 | 103 | silver | 3200 |
| 4 | 104 | bronze | 980 |
| 5 | 105 | gold | 450 |
Expected output (note final_price for order_id=5):
| order_id | customer_rank | subtotal | discount_rate | discounted | final_price |
|---|---|---|---|---|---|
| 1 | platinum | 15000 | 0.20 | 12000 | 12000 |
| 2 | gold | 8500 | 0.10 | 7650 | 7650 |
| 3 | silver | 3200 | 0.05 | 3040 | 3040 |
| 4 | bronze | 980 | 0.00 | 980 | 980 |
| 5 | gold | 450 | 0.10 | 405 | 500 ← floor applied |
order_id=5: subtotal=450 → 10% gold discount → 405 → clamp to the minimum price of 500 → 500.
- 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
This pattern combines FILTER, which applies a condition to an aggregate, with deduplicated string aggregation.
-- FILTER: apply a WHERE-like condition inside an aggregate without changing group size COUNT(*) FILTER (WHERE status = 'completed') -- → count completed rows only SUM(amount) FILTER (WHERE status = 'completed') -- → sum completed amounts only -- STRING_AGG(DISTINCT ...): concatenate strings without duplicates STRING_AGG(DISTINCT product_name, ', ' ORDER BY product_name) -- → include each product name once in lexical order -- Note: ORDER BY can use only the same expression as the DISTINCT argument -- Multiply by 100.0 to use NUMERIC rather than integer division 100.0 * COUNT(*) FILTER (...) / COUNT(*) -- → 66.7 (NUMERIC division) 100 * COUNT(*) FILTER (...) / COUNT(*) -- → 66 (integer division truncates the fraction)
Aggregate the following values by category from the sales table.
① total_sales: all rows, regardless of status
② completed_count: rows where status = 'completed', using FILTER
③ completed_amount: total amount of completed transactions, using FILTER
④ completion_rate: completion percentage, rounded to one decimal place
⑤ products: a deduplicated, lexically sorted product-name list using STRING_AGG(DISTINCT).
Sort the result by category ascending.
| sale_id | category | product_name | amount | status |
|---|---|---|---|---|
| 1 | food | Coffee | 500 | completed |
| 2 | food | Sandwich | 800 | completed |
| 3 | food | Coffee | 500 | cancelled |
| 4 | tech | Mouse | 3000 | completed |
| 5 | tech | Keyboard | 8500 | completed |
| 6 | tech | Mouse | 3000 | refunded |
| 7 | books | SQL Guide | 1500 | completed |
| 8 | books | Design Book | 2200 | cancelled |
Expected output (category ascending):
| category | total_sales | completed_count | completed_amount | completion_rate | products |
|---|---|---|---|---|---|
| books | 2 | 1 | 1500 | 50.0 | Design Book, SQL Guide |
| food | 3 | 2 | 1300 | 66.7 | Coffee, Sandwich |
| tech | 3 | 2 | 11500 | 66.7 | Keyboard, Mouse |
DISTINCT removes duplicate products: Coffee occurs twice in food but appears only once.
- 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
This is an advanced pattern for extracting and reformatting data with regular-expression capture groups ().
-- REGEXP_MATCHES: return matched groups as an array (setof text[]) REGEXP_MATCHES('ERROR 404 /api/users', '(\w+) (\d+) (.*)') -- → {'ERROR','404','/api/users'} each group becomes an array element -- Extract each group with one-based array indexes (REGEXP_MATCHES(log, '(\w+) (\d+) (.*)'))[1] -- → 'ERROR' first group (REGEXP_MATCHES(log, '(\w+) (\d+) (.*)'))[2] -- → '404' second group -- REGEXP_REPLACE: reformat by referencing capture groups REGEXP_REPLACE('20240315', '^(\d{4})(\d{2})(\d{2})$', '\1-\2-\3') -- → '2024-03-15' \1=year, \2=month, \3=day -- Match a pattern with ~, which returns BOOLEAN 'ERROR 404 /api' ~ '^(ERROR|WARN)' -- → true (case-sensitive) 'error 404 /api' ~* '^(error|warn)' -- → true (~* is case-insensitive)
(). REGEXP_MATCHES returns no row for a nonmatch, so use LEFT JOIN, CASE, or the singular REGEXP_MATCH when nonmatching input must be retained.The app_logs table stores log messages as unstructured strings. Parse them with regular expressions and generate the following columns.
① log_level: log level (ERROR, WARN, or INFO)
② status_code: numeric HTTP status code
③ endpoint: API endpoint path
④ formatted_date: transform log_date from 'YYYYMMDD' to 'YYYY-MM-DD'
⑤ is_error: true when the log level is ERROR or WARN, using the ~ operator.
Sort the result by log_id ascending.
| log_id | log_date | log_message |
|---|---|---|
| 1 | 20240315 | ERROR 500 /api/users |
| 2 | 20240315 | INFO 200 /api/health |
| 3 | 20240316 | WARN 429 /api/orders |
| 4 | 20240316 | ERROR 404 /api/products |
| 5 | 20240317 | INFO 200 /api/users |
Expected output (log_id ascending):
| log_id | log_level | status_code | endpoint | formatted_date | is_error |
|---|---|---|---|---|---|
| 1 | ERROR | 500 | /api/users | 2024-03-15 | true |
| 2 | INFO | 200 | /api/health | 2024-03-15 | false |
| 3 | WARN | 429 | /api/orders | 2024-03-16 | true |
| 4 | ERROR | 404 | /api/products | 2024-03-16 | true |
| 5 | INFO | 200 | /api/users | 2024-03-17 | false |
- 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