A function takes values in brackets and returns one value per row: UPPER('pune') returns PUNE. Functions do not change the table — they only change what the query shows. You can nest them: UPPER(LEFT(customer_name, 1)).
6. Functions and CASE
Step 1 of 4 · Lesson
Lesson 6 of 21
MySQL string, number and date functions, COALESCE and CASE
Clean and label data inside a query: CONCAT, UPPER, SUBSTRING, ROUND, COALESCE, IFNULL, YEAR, DATEDIFF and CASE WHEN — with the NULL traps explained.
In short
How does CASE WHEN work in MySQL?
CASE checks conditions in order and returns the value of the first one that is true: CASE WHEN price >= 800 THEN 'premium' WHEN price >= 100 THEN 'mid' ELSE 'budget' END. If nothing matches and there is no ELSE, it returns NULL. Use it in SELECT to label rows or in ORDER BY to control sorting.
By the end of this lesson you can
- Join and reshape text with CONCAT, UPPER, LOWER, SUBSTRING, REPLACE and CHAR_LENGTH
- Round money correctly with ROUND, and know CEIL and FLOOR
- Replace NULL with a default using COALESCE or IFNULL — and know why CONCAT returns NULL
- Pull parts out of dates with YEAR, MONTH, DAYNAME and DATEDIFF
- Label and sort rows with CASE WHEN … THEN … ELSE … END
What a function is
Text functions
| Function | Returns | Example → result |
|---|---|---|
CONCAT(a, b, …) |
joined text | CONCAT('Asha', ' - ', 'Pune') → Asha - Pune |
UPPER(s) / LOWER(s) |
changed case | UPPER('Pen') → PEN |
CHAR_LENGTH(s) |
number of characters | CHAR_LENGTH('Mug') → 3 |
SUBSTRING(s, start, len) |
part of text (start at 1) | SUBSTRING('Keyboard', 1, 3) → Key |
LEFT(s, n) / RIGHT(s, n) |
first / last n characters | RIGHT('Planner', 3) → ner |
REPLACE(s, from, to) |
text with a part swapped | REPLACE('Desk lamp', ' ', '_') → Desk_lamp |
TRIM(s) |
text without outer spaces | TRIM(' Pen ') → Pen |
SUBSTRING_INDEX(s, d, n) |
text before the n-th delimiter (after it when n is negative) | SUBSTRING_INDEX('a@b.test', '@', -1) → b.test |
SELECT customer_name,
CONCAT(customer_name, ' (', city, ')') AS label,
UPPER(LEFT(customer_name, 1)) AS initial,
SUBSTRING_INDEX(email, '@', -1) AS email_domain
FROM customers
ORDER BY customer_id;| customer_name | label | initial | email_domain |
|---|---|---|---|
| Asha | Asha (Pune) | A | example.test |
| Bilal | Bilal (Mumbai) | B | example.test |
| Charu | Charu (Pune) | C | NULL |
| Dev | Dev (Delhi) | D | example.test |
| Esha | Esha (Mumbai) | E | example.test |
| Farah | Farah (Jaipur) | F | example.test |
Look at Charu: her email is NULL, so the domain is NULL too. Functions pass NULL through.
The CONCAT NULL trap
In MySQL, if any argument of CONCAT is NULL, the whole result is NULL. That silently erases labels for rows with missing data:
SELECT customer_name,
CONCAT('Email: ', email) AS plain_concat,
CONCAT_WS(' | ', customer_name, email) AS with_ws
FROM customers
WHERE customer_id IN (1, 3);| customer_name | plain_concat | with_ws |
|---|---|---|
| Asha | Email: asha@example.test | Asha | asha@example.test |
| Charu | NULL | Charu |
CONCAT_WS(separator, …) (with separator) skips NULL values instead. Or replace the NULL first with COALESCE, shown next.
COALESCE and IFNULL: defaults for missing values
COALESCE(a, b, c) returns the first value that is not NULL. IFNULL(a, b) is MySQL's two-argument version. Both are how you print not given instead of an empty cell, or treat a missing amount as 0:
SELECT customer_name,
COALESCE(email, 'not given') AS email_shown,
IFNULL(email, 'not given') AS same_with_ifnull
FROM customers
ORDER BY customer_id;| customer_name | email_shown | same_with_ifnull |
|---|---|---|
| Asha | asha@example.test | asha@example.test |
| Bilal | bilal@example.test | bilal@example.test |
| Charu | not given | not given |
| Dev | dev@example.test | dev@example.test |
| Esha | esha@example.test | esha@example.test |
| Farah | farah@example.test | farah@example.test |
COALESCE is standard SQL and works in every database, so prefer it in shared code. You will use COALESCE(revenue, 0) in lesson 9 to show customers with no orders as 0 instead of NULL.
Number functions
ROUND(x, d) rounds to d decimals (half away from zero). CEIL rounds up and FLOOR rounds down to a whole number. ABS drops the sign and MOD(a, b) gives the remainder.
SELECT product_name, price,
ROUND(price * 1.18, 2) AS price_with_gst,
CEIL(price / 7) AS ceil_week,
FLOOR(price / 7) AS floor_week
FROM products
ORDER BY product_id;| product_name | price | price_with_gst | ceil_week | floor_week |
|---|---|---|---|---|
| Notebook | 100.00 | 118.00 | 15 | 14 |
| Pen | 20.00 | 23.60 | 3 | 2 |
| Desk lamp | 800.00 | 944.00 | 115 | 114 |
| Mug | 250.00 | 295.00 | 36 | 35 |
| Keyboard | 1200.00 | 1416.00 | 172 | 171 |
| Planner | 300.00 | 354.00 | 43 | 42 |
Date functions
Dates are stored as YYYY-MM-DD. These functions extract or compare parts of a date. (Lesson 11 adds DATE_FORMAT and monthly reporting.)
SELECT order_id, order_date,
YEAR(order_date) AS yr,
MONTH(order_date) AS mon,
DAYNAME(order_date) AS weekday,
DATEDIFF('2026-03-31', order_date) AS days_before_quarter_end
FROM orders
ORDER BY order_id
LIMIT 4;| order_id | order_date | yr | mon | weekday | days_before_quarter_end |
|---|---|---|---|---|---|
| 101 | 2026-01-05 | 2026 | 1 | Monday | 85 |
| 102 | 2026-01-08 | 2026 | 1 | Thursday | 82 |
| 103 | 2026-02-03 | 2026 | 2 | Tuesday | 56 |
| 104 | 2026-02-10 | 2026 | 2 | Tuesday | 49 |
DATEDIFF(later, earlier) returns whole days. CURDATE() returns today's date and NOW() the date and time — useful in real reports, but they make answers change every day, so this course uses fixed dates.
CASE WHEN: if-then logic in SQL
CASE checks its WHEN conditions from top to bottom and returns the THEN value of the first true one. ELSE catches everything else; without ELSE you get NULL.
CASE
WHEN condition_1 THEN value_1
WHEN condition_2 THEN value_2
ELSE fallback
END
Because order matters, write the most specific condition first:
SELECT product_name, price,
CASE
WHEN price >= 800 THEN 'premium'
WHEN price >= 100 THEN 'mid'
ELSE 'budget'
END AS price_band
FROM products
ORDER BY price DESC;| product_name | price | price_band |
|---|---|---|
| Keyboard | 1200.00 | premium |
| Desk lamp | 800.00 | premium |
| Planner | 300.00 | mid |
| Mug | 250.00 | mid |
| Notebook | 100.00 | mid |
| Pen | 20.00 | budget |
If you reversed the first two WHENs, Keyboard (1200.00) would be labelled mid, because price >= 100 would be true first.
CASE in ORDER BY
CASE can create a custom sort order — for example, show pending orders first because someone must act on them, then the rest by date:
SELECT order_id, status, order_date
FROM orders
ORDER BY CASE status WHEN 'pending' THEN 1 WHEN 'cancelled' THEN 2 ELSE 3 END,
order_date;| order_id | status | order_date |
|---|---|---|
| 107 | pending | 2026-03-04 |
| 104 | cancelled | 2026-02-10 |
| 101 | completed | 2026-01-05 |
| 102 | completed | 2026-01-08 |
| 103 | completed | 2026-02-03 |
| 105 | completed | 2026-02-15 |
| 106 | completed | 2026-03-02 |
| 108 | completed | 2026-03-11 |
This is the simple CASE form: CASE column WHEN value THEN …. It compares one column with fixed values; the searched form above (CASE WHEN condition) can test anything.
Common mistakes
Wrong
CONCAT('Email: ', email)Fix
CONCAT('Email: ', COALESCE(email, 'not given'))Why: Returns NULL for every customer without an email.
Wrong
CASE WHEN price >= 100 THEN 'mid' WHEN price >= 800 THEN 'premium' ENDFix
Put WHEN price >= 800 first.
Why: The first true condition wins, so nothing is ever premium.
Wrong
SUBSTRING(name, 0, 3)Fix
SUBSTRING(name, 1, 3)Why: SQL strings start at position 1; position 0 returns an empty string.
Wrong
CASE … END without ELSEFix
Always add ELSE 'other' (or another explicit value).
Why: Unmatched rows silently become NULL.
Check yourself
- Why does CONCAT('Email: ', email) show NULL for Charu, and what are two fixes?
- What is the difference between COALESCE and IFNULL?
- In a CASE with several WHENs, which THEN value is returned?
Answer out loud or in your notebook. If one is hard, reread that section before practising.
Frequently asked questions
What is the difference between COALESCE and IFNULL in MySQL?
IFNULL(a, b) takes exactly two arguments and is MySQL-specific. COALESCE(a, b, c, …) takes any number and is standard SQL, so it works in every database.
Why does CONCAT return NULL in MySQL?
Because one of its arguments is NULL. MySQL's CONCAT returns NULL if any argument is NULL. Use CONCAT_WS or wrap the column in COALESCE.
Can I use CASE in WHERE or ORDER BY?
Yes. CASE is an expression, so it works anywhere a value is allowed: SELECT, WHERE, ORDER BY, GROUP BY and inside aggregates such as SUM(CASE WHEN … THEN 1 ELSE 0 END).
Quick quiz — 4 questions
Quick Check: Functions and CASE
Meet Learning Shop
One small fictional shop powers the whole course: 6 customers, 6 products, 8 orders and 14 order lines, small enough to check every answer by eye. The practice editor on this page already contains it. To practise in MySQL Workbench, import it once.
customers.customer_id → orders.customer_id · orders.order_id → order_items.order_id · products.product_id → order_items.product_id
Import in MySQL Workbench
- Connect to your local MySQL server (8.0.16 or newer; 8.4 recommended).
- Choose File → Open SQL Script and open the downloaded file.
- Click the lightning bolt to execute the whole script. The last result shows 6, 6, 8 and 14 rows.
- Refresh the Schemas panel, then select the database:
USE portfolio_retail_lab;
SHOW TABLES;Command line instead: mysql -u root -p < mysql-retail-lab.sql
In the practice editor, the Reset database button does the same as the reset script — instantly and only in your browser.
Let’s do one together
Build a customer contact sheet that never shows a blank cell.
- 1Run the query. Charu's contact column shows NULL — that is the CONCAT trap.
- 2Wrap email in COALESCE(email, 'no email') inside the CONCAT and run again.
- 3Add a column: CASE WHEN email IS NULL THEN 'ask for email' ELSE 'ok' END AS action.
- 4Sort so customers needing action come first: ORDER BY email IS NULL DESC, customer_id.
SELECT customer_name,
UPPER(city) AS city,
CONCAT(customer_name, ' <', email, '>') AS contact
FROM customers
ORDER BY customer_id;What you should see
Six rows. Every contact reads like Asha <asha@example.test>, except Charu, whose contact is NULL because her email is NULL.
| customer_name | city | contact |
|---|---|---|
| Asha | PUNE | Asha <asha@example.test> |
| Bilal | MUMBAI | Bilal <bilal@example.test> |
| Charu | PUNE | NULL |
| Dev | DELHI | Dev <dev@example.test> |
| Esha | MUMBAI | Esha <esha@example.test> |
| Farah | JAIPUR | Farah <farah@example.test> |
Practice problems
17 problems from Easy to Professional · 0 solved · J / K to move
Show every customer name in capital letters.
Target: 6 rows: ASHA, BILAL, CHARU, DEV, ESHA, FARAH
Database already selected: Learning Shop (small)(portfolio_retail_lab)— write the query directly, no USE needed.
I’m stuck — show a hint
UPPER(customer_name).
Reference answer
SELECT UPPER(customer_name) AS name_upper FROM customers ORDER BY customer_id;| name_upper |
|---|
| ASHA |
| BILAL |
| CHARU |
| DEV |
| ESHA |
| FARAH |
More than one query can be correct. If your result matches the target, your query is right.
Show each product name and how many characters it has.
Target: 6 rows: Notebook 8, Pen 3, Desk lamp 9, Mug 3, Keyboard 8, Planner 7
I’m stuck — show a hint
CHAR_LENGTH counts characters, including the space.
Reference answer
SELECT product_name, CHAR_LENGTH(product_name) AS name_length FROM products ORDER BY product_id;| product_name | name_length |
|---|---|
| Notebook | 8 |
| Pen | 3 |
| Desk lamp | 9 |
| Mug | 3 |
| Keyboard | 8 |
| Planner | 7 |
More than one query can be correct. If your result matches the target, your query is right.
Make a label like Asha (Pune) for every customer.
Target: 6 rows: Asha (Pune) … Farah (Jaipur)
I’m stuck — show a hint
CONCAT(customer_name, ' (', city, ')').
Reference answer
SELECT CONCAT(customer_name, ' (', city, ')') AS label FROM customers ORDER BY customer_id;| label |
|---|
| Asha (Pune) |
| Bilal (Mumbai) |
| Charu (Pune) |
| Dev (Delhi) |
| Esha (Mumbai) |
| Farah (Jaipur) |
More than one query can be correct. If your result matches the target, your query is right.
Show each product's price including 18% GST, rounded to 2 decimals.
Target: 6 rows: 118.00, 23.60, 944.00, 295.00, 1416.00, 354.00
I’m stuck — show a hint
ROUND(price * 1.18, 2).
Reference answer
SELECT product_name, ROUND(price * 1.18, 2) AS price_with_gst FROM products ORDER BY product_id;| product_name | price_with_gst |
|---|---|
| Notebook | 118.00 |
| Pen | 23.60 |
| Desk lamp | 944.00 |
| Mug | 295.00 |
| Keyboard | 1416.00 |
| Planner | 354.00 |
More than one query can be correct. If your result matches the target, your query is right.
Show the year and month number of every order.
Target: 8 rows; all 2026; months 1, 1, 2, 2, 2, 3, 3, 3
I’m stuck — show a hint
YEAR() and MONTH().
Reference answer
SELECT order_id, YEAR(order_date) AS yr, MONTH(order_date) AS mon FROM orders ORDER BY order_id;| order_id | yr | mon |
|---|---|---|
| 101 | 2026 | 1 |
| 102 | 2026 | 1 |
| 103 | 2026 | 2 |
| 104 | 2026 | 2 |
| 105 | 2026 | 2 |
| 106 | 2026 | 3 |
| 107 | 2026 | 3 |
| 108 | 2026 | 3 |
More than one query can be correct. If your result matches the target, your query is right.
Show every customer's email, or 'not given' when it is missing, using IFNULL.
Target: 6 rows; Charu shows not given
I’m stuck — show a hint
IFNULL(email, 'not given').
Reference answer
SELECT customer_name, IFNULL(email, 'not given') AS email FROM customers ORDER BY customer_id;| customer_name | |
|---|---|
| Asha | asha@example.test |
| Bilal | bilal@example.test |
| Charu | not given |
| Dev | dev@example.test |
| Esha | esha@example.test |
| Farah | farah@example.test |
More than one query can be correct. If your result matches the target, your query is right.
Label products budget (under 100), mid (100 to 799) or premium (800 and above).
Target: Notebook mid, Pen budget, Desk lamp premium, Mug mid, Keyboard premium, Planner mid
I’m stuck — show a hint
Check the highest band first, end with ELSE.
Reference answer
SELECT product_name, CASE WHEN price >= 800 THEN 'premium' WHEN price >= 100 THEN 'mid' ELSE 'budget' END AS band FROM products ORDER BY product_id;| product_name | band |
|---|---|
| Notebook | mid |
| Pen | budget |
| Desk lamp | premium |
| Mug | mid |
| Keyboard | premium |
| Planner | mid |
More than one query can be correct. If your result matches the target, your query is right.
Show the email domain (the part after @) for every customer.
Target: 6 rows; five show example.test, Charu shows NULL
I’m stuck — show a hint
SUBSTRING_INDEX(email, '@', -1).
Reference answer
SELECT customer_name, SUBSTRING_INDEX(email, '@', -1) AS domain FROM customers ORDER BY customer_id;| customer_name | domain |
|---|---|
| Asha | example.test |
| Bilal | example.test |
| Charu | NULL |
| Dev | example.test |
| Esha | example.test |
| Farah | example.test |
More than one query can be correct. If your result matches the target, your query is right.
Show how many days each order was placed before 2026-03-31.
Target: 8 rows; 101 is 85 days, 108 is 20 days
I’m stuck — show a hint
DATEDIFF(later, earlier).
Reference answer
SELECT order_id, DATEDIFF('2026-03-31', order_date) AS days_before FROM orders ORDER BY order_id;| order_id | days_before |
|---|---|
| 101 | 85 |
| 102 | 82 |
| 103 | 56 |
| 104 | 49 |
| 105 | 44 |
| 106 | 29 |
| 107 | 27 |
| 108 | 20 |
More than one query can be correct. If your result matches the target, your query is right.
Show customer-friendly status text: completed → Delivered, cancelled → Cancelled, pending → Awaiting payment.
Target: 8 rows; 104 Cancelled, 107 Awaiting payment, the rest Delivered
I’m stuck — show a hint
Simple CASE: CASE status WHEN 'completed' THEN …
Reference answer
SELECT order_id, CASE status WHEN 'completed' THEN 'Delivered' WHEN 'cancelled' THEN 'Cancelled' WHEN 'pending' THEN 'Awaiting payment' ELSE status END AS status_text FROM orders ORDER BY order_id;| order_id | status_text |
|---|---|
| 101 | Delivered |
| 102 | Delivered |
| 103 | Delivered |
| 104 | Cancelled |
| 105 | Delivered |
| 106 | Delivered |
| 107 | Awaiting payment |
| 108 | Delivered |
More than one query can be correct. If your result matches the target, your query is right.
Show the weekday name of every order date.
Target: 8 rows; 101 (2026-01-05) is Monday
I’m stuck — show a hint
DAYNAME(order_date).
Reference answer
SELECT order_id, order_date, DAYNAME(order_date) AS weekday FROM orders ORDER BY order_id;| order_id | order_date | weekday |
|---|---|---|
| 101 | 2026-01-05 | Monday |
| 102 | 2026-01-08 | Thursday |
| 103 | 2026-02-03 | Tuesday |
| 104 | 2026-02-10 | Tuesday |
| 105 | 2026-02-15 | Sunday |
| 106 | 2026-03-02 | Monday |
| 107 | 2026-03-04 | Wednesday |
| 108 | 2026-03-11 | Wednesday |
More than one query can be correct. If your result matches the target, your query is right.
Line totals with a bulk discount: lines with quantity 5 or more get 10% off. Round to 2 decimals.
Target: 14 rows; order 101 Pen 100 → 90.00, order 103 Pen 200 → 180.00, every other line full price
I’m stuck — show a hint
ROUND(quantity * unit_price * CASE WHEN quantity >= 5 THEN 0.9 ELSE 1 END, 2).
Reference answer
SELECT order_id, product_id, quantity * unit_price AS gross, ROUND(quantity * unit_price * CASE WHEN quantity >= 5 THEN 0.9 ELSE 1 END, 2) AS net FROM order_items ORDER BY order_id, product_id;| order_id | product_id | gross | net |
|---|---|---|---|
| 101 | 1 | 200.00 | 200.00 |
| 101 | 2 | 100.00 | 90.00 |
| 102 | 3 | 800.00 | 800.00 |
| 102 | 4 | 500.00 | 500.00 |
| 103 | 2 | 200.00 | 180.00 |
| 103 | 5 | 1200.00 | 1200.00 |
| 104 | 3 | 800.00 | 800.00 |
| 105 | 1 | 300.00 | 300.00 |
| 105 | 4 | 250.00 | 250.00 |
| 106 | 1 | 100.00 | 100.00 |
| 106 | 5 | 1200.00 | 1200.00 |
| 107 | 6 | 600.00 | 600.00 |
| 108 | 3 | 800.00 | 800.00 |
| 108 | 4 | 500.00 | 500.00 |
More than one query can be correct. If your result matches the target, your query is right.
Show 'Contact: ' followed by the email for every customer, so that no row is NULL.
Target: 6 rows; Charu shows Contact: none
I’m stuck — show a hint
Plain CONCAT gives NULL for Charu; use COALESCE inside it.
Reference answer
SELECT customer_name, CONCAT('Contact: ', COALESCE(email, 'none')) AS contact FROM customers ORDER BY customer_id;| customer_name | contact |
|---|---|
| Asha | Contact: asha@example.test |
| Bilal | Contact: bilal@example.test |
| Charu | Contact: none |
| Dev | Contact: dev@example.test |
| Esha | Contact: esha@example.test |
| Farah | Contact: farah@example.test |
More than one query can be correct. If your result matches the target, your query is right.
List products with Electronics first, then all other products by price, cheapest first.
Target: Desk lamp, Keyboard, then Pen, Notebook, Mug, Planner
I’m stuck — show a hint
ORDER BY CASE WHEN category = 'Electronics' THEN 0 ELSE 1 END, price.
Reference answer
SELECT product_name, category, price FROM products ORDER BY CASE WHEN category = 'Electronics' THEN 0 ELSE 1 END, price;| product_name | category | price |
|---|---|---|
| Desk lamp | Electronics | 800.00 |
| Keyboard | Electronics | 1200.00 |
| Pen | Stationery | 20.00 |
| Notebook | Stationery | 100.00 |
| Mug | Home | 250.00 |
| Planner | Stationery | 300.00 |
More than one query can be correct. If your result matches the target, your query is right.
Mask emails for a screenshot: first letter, then ***, then @ and the domain.
Target: 6 rows; Asha → a***@example.test; Charu → NULL
I’m stuck — show a hint
CONCAT(LEFT(email, 1), '***@', SUBSTRING_INDEX(email, '@', -1)).
Reference answer
SELECT customer_name, CONCAT(LEFT(email, 1), '***@', SUBSTRING_INDEX(email, '@', -1)) AS masked_email FROM customers ORDER BY customer_id;| customer_name | masked_email |
|---|---|
| Asha | a***@example.test |
| Bilal | b***@example.test |
| Charu | NULL |
| Dev | d***@example.test |
| Esha | e***@example.test |
| Farah | f***@example.test |
More than one query can be correct. If your result matches the target, your query is right.
Customer contact sheet: name in capitals, city, email or 'no email', and action = 'ask for email' when missing else 'ok'. Missing emails first, then by name.
Target: 6 rows; CHARU first with no email / ask for email, then ASHA, BILAL, DEV, ESHA, FARAH
I’m stuck — show a hint
Sort by a CASE that puts NULL emails first.
Reference answer
SELECT UPPER(customer_name) AS customer, city, COALESCE(email, 'no email') AS email, CASE WHEN email IS NULL THEN 'ask for email' ELSE 'ok' END AS action FROM customers ORDER BY CASE WHEN email IS NULL THEN 0 ELSE 1 END, customer_name;| customer | city | action | |
|---|---|---|---|
| CHARU | Pune | no email | ask for email |
| ASHA | Pune | asha@example.test | ok |
| BILAL | Mumbai | bilal@example.test | ok |
| DEV | Delhi | dev@example.test | ok |
| ESHA | Mumbai | esha@example.test | ok |
| FARAH | Jaipur | farah@example.test | ok |
More than one query can be correct. If your result matches the target, your query is right.
Invoice text for order 101: one line per item like 'Product 1 x2 = 200.00'.
Target: 2 rows: Product 1 x2 = 200.00; Product 2 x5 = 100.00
I’m stuck — show a hint
FORMAT(quantity * unit_price, 2) turns the amount into text with 2 decimals.
Reference answer
SELECT CONCAT('Product ', product_id, ' x', quantity, ' = ', FORMAT(quantity * unit_price, 2)) AS invoice_line FROM order_items WHERE order_id = 101 ORDER BY product_id;| invoice_line |
|---|
| Product 1 x2 = 200.00 |
| Product 2 x5 = 100.00 |
More than one query can be correct. If your result matches the target, your query is right.
Practice mode runs in your browser on a SQLite engine with a MySQL compatibility layer, so you can practise without installing anything. Your result is compared with the reference answer on a fresh copy of the same rows, so any correct query is accepted. A few MySQL-only features (EXPLAIN columns, WITH ROLLUP, user variables) behave differently here — run those in MySQL Workbench.
Finished? Save your queries in a .sql file and mark the lesson complete.
Answers — Functions and CASE
Easy. Show every customer name in capital letters. Target: 6 rows: ASHA, BILAL, CHARU, DEV, ESHA, FARAH
SELECT UPPER(customer_name) AS name_upper FROM customers ORDER BY customer_id;
Easy. Show each product name and how many characters it has. Target: 6 rows: Notebook 8, Pen 3, Desk lamp 9, Mug 3, Keyboard 8, Planner 7
SELECT product_name, CHAR_LENGTH(product_name) AS name_length FROM products ORDER BY product_id;
Easy. Make a label like Asha (Pune) for every customer. Target: 6 rows: Asha (Pune) … Farah (Jaipur)
SELECT CONCAT(customer_name, ' (', city, ')') AS label FROM customers ORDER BY customer_id;Easy. Show each product's price including 18% GST, rounded to 2 decimals. Target: 6 rows: 118.00, 23.60, 944.00, 295.00, 1416.00, 354.00
SELECT product_name, ROUND(price * 1.18, 2) AS price_with_gst FROM products ORDER BY product_id;
Easy. Show the year and month number of every order. Target: 8 rows; all 2026; months 1, 1, 2, 2, 2, 3, 3, 3
SELECT order_id, YEAR(order_date) AS yr, MONTH(order_date) AS mon FROM orders ORDER BY order_id;
Medium. Show every customer's email, or 'not given' when it is missing, using IFNULL. Target: 6 rows; Charu shows not given
SELECT customer_name, IFNULL(email, 'not given') AS email FROM customers ORDER BY customer_id;
Medium. Label products budget (under 100), mid (100 to 799) or premium (800 and above). Target: Notebook mid, Pen budget, Desk lamp premium, Mug mid, Keyboard premium, Planner mid
SELECT product_name, CASE WHEN price >= 800 THEN 'premium' WHEN price >= 100 THEN 'mid' ELSE 'budget' END AS band FROM products ORDER BY product_id;
Medium. Show the email domain (the part after @) for every customer. Target: 6 rows; five show example.test, Charu shows NULL
SELECT customer_name, SUBSTRING_INDEX(email, '@', -1) AS domain FROM customers ORDER BY customer_id;
Medium. Show how many days each order was placed before 2026-03-31. Target: 8 rows; 101 is 85 days, 108 is 20 days
SELECT order_id, DATEDIFF('2026-03-31', order_date) AS days_before FROM orders ORDER BY order_id;Medium. Show customer-friendly status text: completed → Delivered, cancelled → Cancelled, pending → Awaiting payment. Target: 8 rows; 104 Cancelled, 107 Awaiting payment, the rest Delivered
SELECT order_id, CASE status WHEN 'completed' THEN 'Delivered' WHEN 'cancelled' THEN 'Cancelled' WHEN 'pending' THEN 'Awaiting payment' ELSE status END AS status_text FROM orders ORDER BY order_id;
Medium. Show the weekday name of every order date. Target: 8 rows; 101 (2026-01-05) is Monday
SELECT order_id, order_date, DAYNAME(order_date) AS weekday FROM orders ORDER BY order_id;
Hard. Line totals with a bulk discount: lines with quantity 5 or more get 10% off. Round to 2 decimals. Target: 14 rows; order 101 Pen 100 → 90.00, order 103 Pen 200 → 180.00, every other line full price
SELECT order_id, product_id, quantity * unit_price AS gross, ROUND(quantity * unit_price * CASE WHEN quantity >= 5 THEN 0.9 ELSE 1 END, 2) AS net FROM order_items ORDER BY order_id, product_id;
Hard. Show 'Contact: ' followed by the email for every customer, so that no row is NULL. Target: 6 rows; Charu shows Contact: none
SELECT customer_name, CONCAT('Contact: ', COALESCE(email, 'none')) AS contact FROM customers ORDER BY customer_id;Hard. List products with Electronics first, then all other products by price, cheapest first. Target: Desk lamp, Keyboard, then Pen, Notebook, Mug, Planner
SELECT product_name, category, price FROM products ORDER BY CASE WHEN category = 'Electronics' THEN 0 ELSE 1 END, price;
Hard. Mask emails for a screenshot: first letter, then ***, then @ and the domain. Target: 6 rows; Asha → a***@example.test; Charu → NULL
SELECT customer_name, CONCAT(LEFT(email, 1), '***@', SUBSTRING_INDEX(email, '@', -1)) AS masked_email FROM customers ORDER BY customer_id;
Professional. Customer contact sheet: name in capitals, city, email or 'no email', and action = 'ask for email' when missing else 'ok'. Missing emails first, then by name. Target: 6 rows; CHARU first with no email / ask for email, then ASHA, BILAL, DEV, ESHA, FARAH
SELECT UPPER(customer_name) AS customer, city, COALESCE(email, 'no email') AS email, CASE WHEN email IS NULL THEN 'ask for email' ELSE 'ok' END AS action FROM customers ORDER BY CASE WHEN email IS NULL THEN 0 ELSE 1 END, customer_name;
Professional. Invoice text for order 101: one line per item like 'Product 1 x2 = 200.00'. Target: 2 rows: Product 1 x2 = 200.00; Product 2 x5 = 100.00
SELECT CONCAT('Product ', product_id, ' x', quantity, ' = ', FORMAT(quantity * unit_price, 2)) AS invoice_line FROM order_items WHERE order_id = 101 ORDER BY product_id;
Up next · Lesson 7
GROUP BY and HAVING
90 min · 19 practice problems