TO_CHAR(value, format) converts a date or timestamp into an arbitrary string. DATE_TRUNC('unit', timestamp) truncates a timestamp to the start of the specified unit (month, day, hour, etc.). This is the reverse direction of CAST from the basics (date type → string).
TO_CHAR('2024-05-15 14:30:00'::TIMESTAMP, 'YYYY/MM/DD') -- → '2024/05/15' TO_CHAR('2024-05-15 14:30:00'::TIMESTAMP, 'HH24:MI') -- → '14:30' DATE_TRUNC('month', '2024-05-15 14:30:00'::TIMESTAMP) -- → 2024-05-01 00:00:00 DATE_TRUNC('day', '2024-05-15 14:30:00'::TIMESTAMP) -- → 2024-05-15 00:00:00
YYYY=4-digit year, MM=2-digit month, DD=2-digit day, HH24=hour in 24-hour clock, MI=minutes, SS=seconds. DATE_TRUNC accepts units such as 'year'/'month'/'week'/'day'/'hour'.TO_CHAR(DATE_TRUNC(...), 'YYYY-MM') or cast it with ::DATE.The orders table has an ordered_at column of type TIMESTAMP. Add the following three columns and retrieve the data: ①ordered_at_jp: a formatted date ('YYYY/MM/DD'), ②ordered_time: the time portion ('HH24:MI'), ③order_month: a monthly aggregation key ('YYYY-MM' format). Output in ascending order of order_id.
| order_id | amount | ordered_at (TIMESTAMP) |
|---|---|---|
| 1 | 8000 | 2024-05-01 09:15:00 |
| 2 | 12500 | 2024-05-15 14:30:00 |
| 3 | 3200 | 2024-06-02 10:00:00 |
| 4 | 6700 | 2024-06-18 16:45:00 |
| 5 | 450 | 2024-06-30 23:59:00 |
Expected output:
| order_id | amount | ordered_at_jp | ordered_time | order_month |
|---|---|---|---|---|
| 1 | 8000 | 2024/05/01 | 09:15 | 2024-05 |
| 2 | 12500 | 2024/05/15 | 14:30 | 2024-05 |
| 3 | 3200 | 2024/06/02 | 10:00 | 2024-06 |
| 4 | 6700 | 2024/06/18 | 16:45 | 2024-06 |
| 5 | 450 | 2024/06/30 | 23:59 | 2024-06 |
※ order_month can be used directly as the key of a monthly aggregation query with GROUP BY order_month.
- 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
SPLIT_PART(string, delimiter, field_number) splits a string by a delimiter and returns the nth field. The field number is 1-based. REGEXP_REPLACE(string, pattern, replacement, flags) replaces the parts that match the regular expression with other characters.
SPLIT_PART('alice@example.com', '@', 1) -- → 'alice' (1st) SPLIT_PART('alice@example.com', '@', 2) -- → 'example.com' (2nd) SPLIT_PART('a.b.c', '.', 2) -- → 'b' REGEXP_REPLACE('090-1234-5678', '-', '', 'g') -- → '09012345678' ('g'=replace all) REGEXP_REPLACE('090-1234-5678', '-', '') -- → '0901234-5678' (flag omitted=first only)
'g'=global (replace all), 'i'=case-insensitive (ignore case), 'gi'=both. Omitting the flag replaces only the first match. When you need to replace all, such as removing hyphens from a phone number, 'g' is required.Decompose email in the contacts table with SPLIT_PART into email_user (left of @) and email_domain (right of @), and retrieve phone_normalized, in which every hyphen in phone is removed with REGEXP_REPLACE. Output in ascending order of id.
| id | phone | |
|---|---|---|
| 1 | alice@example.com | 090-1234-5678 |
| 2 | bob.smith@gmail.com | 03-5678-9012 |
| 3 | carol@company.co.jp | 06-1234-5678 |
Expected output:
| id | email_user | email_domain | phone_normalized |
|---|---|---|---|
| 1 | alice | example.com | 09012345678 |
| 2 | bob.smith | gmail.com | 0356789012 |
| 3 | carol | company.co.jp | 0612345678 |
- 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
ROUND(number, decimal_places) rounds to the specified number of decimal places. Omitting the places rounds to an integer. CEIL(number) rounds up, and FLOOR(number) rounds down.
ROUND(734.4) -- → 734 (round half up, places omitted = integer) ROUND(734.5) -- → 735 (0.5 rounds up) CEIL(734.1) -- → 735 (any fraction always adds 1) FLOOR(734.9) -- → 734 (always drops the fraction) ROUND(9.999, 2) -- → 10.00 (round at the 2nd decimal place)
1 / 3 is 0 (integer ÷ integer = integer). To get decimals, you must convert one side to NUMERIC, as in 1::NUMERIC / 3 or 1.0 / 3. Check the types before using ROUND.ROUND(price * (1 + CASE ... END)), a CASE expression can be inlined into a computation. The tax-included calculation is completed in one line without a subquery.From the products table, for products whose category is 'food' apply a reduced tax rate of 8%, and 10% otherwise, and retrieve the tax-included price (price_with_tax, made an integer with ROUND) together with the price band rank of price (price_rank: 3000 yen or more='premium' / 1000 yen or more='standard' / below that='budget'). Output in ascending order of product_id.
| product_id | name | price | category |
|---|---|---|---|
| 1 | Coffee Beans | 1800 | food |
| 2 | Mug | 2200 | goods |
| 3 | Cake | 680 | food |
| 4 | Gift Set | 5400 | gift |
| 5 | Eco Bag | 980 | goods |
Expected output (the tax rate is determined dynamically with CASE WHEN):
| product_id | name | price | tax_rate | price_with_tax | price_rank |
|---|---|---|---|---|---|
| 1 | Coffee Beans | 1800 | 0.08 | 1944 | standard |
| 2 | Mug | 2200 | 0.10 | 2420 | standard |
| 3 | Cake | 680 | 0.08 | 734 | budget |
| 4 | Gift Set | 5400 | 0.10 | 5940 | premium |
| 5 | Eco Bag | 980 | 0.10 | 1078 | budget |
※ Cake: 680×1.08=734.4 → ROUND=734. price_rank is judged on the tax-exclusive price.
- 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 that combines the COALESCE / NULLIF from the basics with GROUP BY aggregation. SUM / AVG automatically ignore NULLs within a group, but if an entire group is NULL, SUM itself returns NULL. Also, NULLIF is especially frequent in aggregation queries as a zero-division-prevention pattern.
SUM(points) -- Sum ignoring NULL rows (returns NULL if all rows are NULL) COALESCE(SUM(points), 0) -- Replace with 0 if SUM is NULL (all rows NULL) SUM(CASE WHEN completed THEN 1 ELSE 0 END) -- Conditional count numerator / NULLIF(denominator, 0) -- Return NULL when the denominator is 0 to prevent zero division
AVG gives (0+10)/2 = 5. If you want to include NULL as 0 in the average, write AVG(COALESCE(col, 0)). Always confirm which is intended.2 / 3 is 0. For calculations that need decimals, such as a completion rate, convert the numerator or denominator to ::NUMERIC before dividing.Group the tasks table by project_id and compute: ①task_count (total number of tasks), ②completed_count (number of completed tasks), ③total_points (sum, treating NULL as 0), ④completion_rate (completion rate %, made an integer with ROUND, zero-division prevented). Output in ascending order of project_id.
| task_id | project_id | points | completed |
|---|---|---|---|
| 1 | A | 10 | true |
| 2 | A | NULL | false |
| 3 | A | 5 | true |
| 4 | B | 8 | false |
| 5 | B | NULL | false |
| 6 | C | 3 | true |
Expected output:
| project_id | task_count | completed_count | total_points | completion_rate |
|---|---|---|---|---|
| A | 3 | 2 | 15 | 67 |
| B | 2 | 0 | 8 | 0 |
| C | 1 | 1 | 3 | 100 |
※ A: SUM(10,NULL,5)=15 (NULL ignored). Completion rate=ROUND(2::NUMERIC/3*100)=67. Even though B has 0 completed, NULLIF avoids a zero-division error.
- 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 practice you often nest multiple transformation functions. In this problem, we combine the functions learned so far to shape the information needed for an invoice in a single query.
-- Function nesting: applied from the inside out 'INV-' || LPAD(invoice_id::TEXT, 6, '0') -- → 'INV-000001' TO_CHAR(ROUND(amount * 1.1), 'FM999,999,999') -- → '8,800' (FM=strip leading spaces) COALESCE(TO_CHAR(paid_at, 'YYYY-MM-DD'), 'Unpaid') -- → date or 'Unpaid'
TO_CHAR(1234, '999,999') yields a leading space, as in ' 1,234'. Adding FM as 'FM999,999' removes the leading space (FM = Fill Mode). This is important when displaying numbers as strings.invoice_id::TEXT.From the invoices table, generate the following columns: ①invoice_no: 'INV-' + 6-digit zero-padded ID (e.g. 'INV-000001'), ②amount_with_tax: tax-included amount (10%, ROUND, comma-separated format), ③issued_at_jp: the formatted issue date, ④paid_status: the payment date if paid_at is non-NULL, or 'Unpaid' if NULL, ⑤payment_label: a payment-status label with CASE WHEN ('paid'→'Paid', 'pending'→'Awaiting Payment', 'overdue'→'Overdue'). Output in ascending order of invoice_id.
| invoice_id | amount | issued_at | paid_at | status |
|---|---|---|---|---|
| 1 | 8000 | 2024-05-01 | 2024-05-10 | paid |
| 2 | 125000 | 2024-05-15 | NULL | pending |
| 3 | 3200 | 2024-06-02 | NULL | overdue |
| 42 | 6700 | 2024-06-18 | 2024-06-25 | paid |
Expected output:
| invoice_no | amount_with_tax | issued_at_jp | paid_status | payment_label |
|---|---|---|---|---|
| INV-000001 | 8,800 | 2024/05/01 | 2024-05-10 | Paid |
| INV-000002 | 137,500 | 2024/05/15 | Unpaid | Awaiting Payment |
| INV-000003 | 3,520 | 2024/06/02 | Unpaid | Overdue |
| INV-000042 | 7,370 | 2024/06/18 | 2024-06-25 | Paid |
※ FM999,999,999 gives comma separation with no leading space. 6700×1.1=7370 (no fraction). 125000×1.1=137500.
- 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