CASE and missing values
In short: CASE WHEN returns a different value depending on a condition, COALESCE replaces NULL with a default value, and NULLIF turns a given value into NULL.
Problem
Words instead of codes
On the “My orders” page, the customer sees the status as it is in the database: paid, shipped. The codes are handy for the program, but the customer expects “Paid” and “On its way”.
The website also wants a “cheap, mid-range, expensive” filter. There is no such column in the database, only a price.
The idea
CASE WHEN
CASE gives a different value depending on a condition:
The conditions are checked from top to bottom. The first true WHEN gives the value of the THEN next to it, and the rest are not looked at. ELSE is for everything for which nothing is true. END closes the expression, and AS gives it a name.
When you compare one column with several values, there is a shorter form: the column is written once after CASE, and each WHEN holds only the value:
Try it
The statuses in words
Show the number, the date and the status in words of every order, in a column called status_text: new is “Received”, paid is “Paid”, shipped is “On its way”, delivered is “Delivered”, cancelled is “Cancelled”.
Solution
SELECT id, ordered_at,
CASE status
WHEN 'new' THEN 'Received'
WHEN 'paid' THEN 'Paid'
WHEN 'shipped' THEN 'On its way'
WHEN 'delivered' THEN 'Delivered'
WHEN 'cancelled' THEN 'Cancelled'
END AS status_text
FROM orders;Five WHENs in the short form CASE status … END AS status_text. If a new status ever appears, without ELSE it will come out as NULL. At least you will notice it.
See it
The expensive ones vanished
A colleague rewrites the price groups with >. Run it and look at the kitchen:
One more thing: if there is no ELSE and no WHEN is true, the result is NULL. The bread gets a note only when it is sold out or running low:
Sometimes that is exactly what you want. If not, write ELSE.
The idea
COALESCE and NULLIF
COALESCE(a, b, …) returns the first value that is not NULL. That is how you put a word in place of what is missing:
Sometimes “missing” is stored not as NULL but as empty text. In the two examples below the database has one more customer, Anna, saved through a form that sends '' instead of NULL. COALESCE does not catch her, because empty text is a value:
NULLIF(a, b) gives NULL when a equals b, and a otherwise. That way the empty text becomes NULL, and COALESCE catches both customers:
More precisely, if you are curious
NULLIF is also used for division: total / NULLIF(count, 0). When count is 0, the divisor becomes NULL and so does the result. In SQLite division by zero gives NULL anyway, but in PostgreSQL it is an error that stops the whole query.
Try it
The line for the courier
Fix the “name, city” line from the lesson on text: when the city is missing, it should say “no city” in its place. The column should be called contact.
Solution
SELECT name || ', ' || COALESCE(city, 'no city') AS contact FROM customers;name || ', ' || COALESCE(city, 'no city') AS contact. Now Boris Mitev has a line: “Boris Mitev, no city”.
Exercise
Two tasks
1. Next to every product, the website shows the stock in words: “out of stock” at 0, “last few” under 10, otherwise “in stock”. Show the name, the stock and the words in a column called availability.
The stock in words for every product:
Solution
SELECT name, stock,
CASE
WHEN stock = 0 THEN 'out of stock'
WHEN stock < 10 THEN 'last few'
ELSE 'in stock'
END AS availability
FROM products;First WHEN stock = 0, then WHEN stock < 10, and ELSE last. If you swap the first two, the sold-out products become “last few”, because 0 is also under 10.
2. The warehouse works in this order: first it packs the paid orders (paid), then looks at the new ones (new), and last checks the shipped ones (shipped). Show the number, status and date of these orders in working order, and within one status the oldest first. With the same date, the smaller number first.
The open orders in the order the warehouse handles them:
Solution
SELECT id, status, ordered_at
FROM orders
WHERE status IN ('paid', 'new', 'shipped')
ORDER BY CASE status WHEN 'paid' THEN 1 WHEN 'new' THEN 2 ELSE 3 END, ordered_at, id;ORDER BY CASE status WHEN 'paid' THEN 1 WHEN 'new' THEN 2 ELSE 3 END, ordered_at, id. CASE turns the status into a number to sort by. Here the check also looks at the order.
More precisely, if you are curious
SQLite also has IIF(condition, if true, if not): a short CASE with one WHEN and an ELSE. SQL Server has it too, but PostgreSQL does not. CASE is the standard form and works everywhere.
Test
Check yourself
Related lessons
Section titled "Related lessons"- Builds on: NULL: no value · ORDER BY, LIMIT and DISTINCT · Arithmetic in a query · String functions
- Continues in: Aggregate functions: COUNT, SUM, AVG · Many tables, and a table joined to itself · A report in steps: WITH · Trees and sequences: WITH RECURSIVE
- Tasks on this topic: Expressions and functions