# Conditional and NULL functions
> Choose a value with CASE, replace NULL with coalesce, and turn a value into NULL with nullif.
- Category: Functions — conditional and NULL
- Keywords: if, if then else, else, switch, decode, iif, ifnull, nvl, isnull, zeroifnull, default value, empty text, missing value, three-valued logic, unknown, NULL
- See also: [WHERE](where.md), [Operators](operators.md), [Conversion functions](functions-conversion.md), [Aggregate functions (sum)](functions-aggregate.md#sum), [GROUP BY and HAVING](group-by.md)
These tools choose a value for each row.
- `CASE` is SQL's "if ... then ... else". It gives the value of the first condition that is true.
- `coalesce` gives the first value that is not `NULL`. Use it to show `0` or `'-'` instead of an empty cell.
- `nullif` does the opposite: it turns one value (like `0` or `''`) into `NULL`.
`NULL` means "no value" or "not known". The last entry, [NULL rules](functions-conditional.md#null-rules), lists how every part of KDB treats it.
**Example: Label each CITY day as up, down or flat** (sample data: sample)
```sql
SELECT time, open, close,
CASE WHEN close > open THEN 'up'
WHEN close < open THEN 'down'
ELSE 'flat' END AS direction
FROM ochl_day
WHERE symbol = 'CITY'
LIMIT 5;
```
Result:
| time (TIMESTAMP) | open (DOUBLE) | close (DOUBLE) | direction (STRING) |
|---|---|---|---|
| 2025-12-28T00:00:00.000Z | 540.3 | 542.2 | up |
| 2025-12-29T00:00:00.000Z | 540.6 | 539.7 | down |
| 2025-12-30T00:00:00.000Z | 538.5 | 539.3 | up |
| 2025-12-31T00:00:00.000Z | 540.4 | 542.4 | up |
| 2026-08-23T00:00:00.000Z | 505 | 507.9 | up |
(5 rows)
The examples below also use two small tables: `ledger` (an accounting book with money in `DECIMAL(2)`) and `app_users` (people who signed up in an app).
Setup used by the examples below:
```sql
CREATE TABLE ledger (
id INT PRIMARY KEY,
day DATE,
account STRING,
debit DECIMAL(2),
credit DECIMAL(2),
note STRING
);
INSERT INTO ledger VALUES
(1, '2026-08-05', 'Cash', 5000.00, NULL, 'Opening balance'),
(2, '2026-08-20', 'Rent', 1200.50, NULL, NULL),
(3, '2026-09-01', 'Cash', NULL, 750.25, 'Shop sale'),
(4, '2026-09-03', 'Bank', 300.00, NULL, '');
CREATE TABLE app_users (
id INT PRIMARY KEY,
name STRING NOT NULL,
phone STRING,
email STRING,
plan STRING,
signed_up TIMESTAMP
);
INSERT INTO app_users VALUES
(1, 'Sita', '9841000001', 'sita@example.com', 'pro', '2026-09-08T10:00:00+05:45'),
(2, 'Ram', NULL, 'ram@example.com', 'free', '2026-09-08T18:30:00+05:45'),
(3, 'Hari', '9851000003', NULL, NULL, '2026-09-09T09:15:00+05:45'),
(4, 'Gita', NULL, NULL, 'trial', '2026-09-10T12:00:00+05:45');
```
## CASE WHEN
Syntax:
```sql
CASE WHEN condition THEN result
[WHEN condition THEN result ...]
[ELSE other_result]
END
```
KDB checks the conditions from top to bottom. The result of the **first** true condition is the answer. When no condition is true, the answer is the `ELSE` value, or `NULL` when there is no `ELSE`.
| Part | Type | Meaning |
|---|---|---|
| `condition` | true/false | a test like `close > open` or `plan IS NULL` |
| `result` | any | the value when this condition is true |
| `other_result` | any | the value when no condition is true (optional) |
- **Returns:** the value of the chosen branch.
- **NULL:** a condition that is `NULL` counts as not true, so KDB goes on to the next `WHEN`. Without `ELSE`, no match gives `NULL`.
- **Notes:**
- A condition must be true, false or `NULL`. `CASE WHEN 1 THEN ...` is an error (write `WHEN x = 1`).
- Only the chosen branch is worked out. The other results are not computed.
- You can use `CASE` everywhere a value can go: `SELECT`, `WHERE`, `ORDER BY`, `GROUP BY`, inside `sum(...)`, and inside another `CASE`.
- KDB does not make the branches one type. `CASE WHEN ... THEN 1 ELSE 'low' END` gives a number in some rows and text in others. Give every branch the same type.
- `GROUP BY` cannot use the name after `AS`. Repeat the whole `CASE` in `GROUP BY` (see the example).
**Example: Live price moves in five groups** (sample data: sample)
```sql
SELECT symbol, ltp, pct_change,
CASE WHEN pct_change >= 2 THEN 'strong up'
WHEN pct_change > 0 THEN 'up'
WHEN pct_change = 0 THEN 'flat'
WHEN pct_change > -2 THEN 'down'
ELSE 'strong down' END AS move
FROM live_data
LATEST ON timestamp PARTITION BY symbol;
```
Result:
| symbol (SYMBOL) | ltp (DOUBLE) | pct_change (DOUBLE) | move (STRING) |
|---|---|---|---|
| CITY | 527.1 | 2.71 | strong up |
| COAST | 401.9 | 0.7 | up |
| TELCO | 897.7 | 1.9 | up |
| VOLT | 184 | -0.86 | down |
(4 rows)
**Example: Count up days and down days per symbol (CASE inside sum)** (sample data: sample)
```sql
SELECT symbol,
sum(CASE WHEN close > open THEN 1 ELSE 0 END) AS up_days,
sum(CASE WHEN close < open THEN 1 ELSE 0 END) AS down_days,
count(*) AS days
FROM ochl_day
GROUP BY symbol
ORDER BY symbol;
```
Result:
| symbol (SYMBOL) | up_days (LONG) | down_days (LONG) | days (LONG) |
|---|---|---|---|
| CITY | 12 | 7 | 19 |
| COAST | 8 | 11 | 19 |
| VOLT | 10 | 9 | 19 |
(3 rows)
**Example: Group rows by a CASE: repeat it in GROUP BY**
```sql
SELECT CASE WHEN phone IS NULL THEN 'no phone' ELSE 'has phone' END AS phone_status,
count(*) AS users
FROM app_users
GROUP BY CASE WHEN phone IS NULL THEN 'no phone' ELSE 'has phone' END
ORDER BY phone_status;
```
Result:
| phone_status (STRING) | users (LONG) |
|---|---|
| has phone | 2 |
| no phone | 2 |
(2 rows)
**Example: Or group by the AS name, which is shorter**
```sql
SELECT CASE WHEN phone IS NULL THEN 'no phone' ELSE 'has phone' END AS phone_status,
count(*) AS users
FROM app_users
GROUP BY phone_status
ORDER BY phone_status;
```
Result:
| phone_status (STRING) | users (LONG) |
|---|---|
| has phone | 2 |
| no phone | 2 |
(2 rows)
**Example: The condition must be true or false** (shows an error)
```sql
SELECT CASE WHEN 1 THEN 'yes' ELSE 'no' END;
```
Result:
Error: `line 1, column 18: a condition must be true or false, got '1'`
## CASE value WHEN
Syntax:
```sql
CASE value
WHEN match THEN result
[WHEN match THEN result ...]
[ELSE other_result]
END
```
The short form. KDB compares `value` with each `match`, like `value = match`, and gives the result of the first one that is equal.
| Part | Type | Meaning |
|---|---|---|
| `value` | any | the value to look at, worked out once |
| `match` | any | a value to compare with |
| `result`, `other_result` | any | as in [CASE WHEN](functions-conditional.md#case) |
- **Returns:** the value of the chosen branch.
- **NULL:** `NULL` never matches, not even `WHEN NULL`. Use `CASE WHEN value IS NULL THEN ...` for that.
- **Notes:**
- Numbers match numeric text: `CASE 5 WHEN '5' THEN ...` matches.
- Values that cannot be compared (a number and `'abc'`) simply do not match. There is no error, unlike `=`.
**Example**
```sql
SELECT CASE 'COAST' WHEN 'CITY' THEN 1 WHEN 'COAST' THEN 2 ELSE 0 END AS a,
CASE 5 WHEN '5' THEN 'text five matches' END AS b,
CASE NULL WHEN NULL THEN 'matched' ELSE 'no match' END AS c;
```
Result:
| a (LONG) | b (STRING) | c (STRING) |
|---|---|---|
| 2 | text five matches | no match |
(1 row)
**Example: Sort sectors in your own order** (sample data: sample)
```sql
SELECT symbol, sector
FROM securities
ORDER BY CASE sector WHEN 'Banking' THEN 1
WHEN 'Energy' THEN 2
ELSE 3 END,
symbol;
```
Result:
| symbol (SYMBOL) | sector (STRING) |
|---|---|
| CITY | Banking |
| COAST | Banking |
| VOLT | Energy |
| ACME | Investment |
| TELCO | Telecom |
(5 rows)
**Example: Plan names for an app report**
```sql
SELECT name,
CASE plan WHEN 'pro' THEN 'Paid'
WHEN 'trial' THEN 'Trial (14 days)'
WHEN 'free' THEN 'Free'
ELSE 'Not chosen' END AS plan_name
FROM app_users
ORDER BY id;
```
Result:
| name (STRING) | plan_name (STRING) |
|---|---|
| Sita | Paid |
| Ram | Free |
| Hari | Not chosen |
| Gita | Trial (14 days) |
(4 rows)
**Example: END is missing** (shows an error)
```sql
SELECT CASE plan WHEN 'pro' THEN 'Paid' FROM app_users;
```
Result:
Error: `line 1, column 41: expected an operator (AND, OR, =, +, ...), WHEN, ELSE or END to close the CASE that starts at line 1, column 8 but found 'FROM'`
## coalesce
Syntax:
```sql
coalesce(value, value, ...)
```
Gives the first value that is not `NULL`. `coalesce(phone, 'no phone')` shows the phone, or `'no phone'` when there is none.
| Argument | Type | Meaning |
|---|---|---|
| `value` | any | 1 to 64 values, checked from left to right |
- **Returns:** the first value that is not `NULL`, with its own type.
- **NULL:** `NULL` only when every value is `NULL`.
- **Notes:**
- KDB stops at the first value that is not `NULL`. The values after it are not worked out.
- The values keep their types. `coalesce(debit, 0)` gives a `DECIMAL(2)` in some rows and the whole number `0` in others. Then `sum` of it becomes a `DOUBLE`. For exact money write `coalesce(debit, 0::DECIMAL(2))`.
- Other databases call this `ifnull`, `nvl` or `isnull`. KDB has only `coalesce`.
**Example**
```sql
SELECT coalesce(NULL, 'n/a') AS a, coalesce(NULL, NULL, 3) AS b, coalesce(7, 8) AS c, coalesce(NULL, NULL) AS d;
```
Result:
| a (STRING) | b (LONG) | c (LONG) | d (STRING) |
|---|---|---|---|
| n/a | 3 | 7 | NULL |
(1 row)
**Example: Show something instead of an empty cell**
```sql
SELECT name,
coalesce(phone, email, 'no contact') AS contact,
coalesce(plan, 'free') AS plan
FROM app_users
ORDER BY id;
```
Result:
| name (STRING) | contact (STRING) | plan (STRING) |
|---|---|---|
| Sita | 9841000001 | pro |
| Ram | ram@example.com | free |
| Hari | 9851000003 | free |
| Gita | no contact | trial |
(4 rows)
**Example: Ledger totals per account: keep DECIMAL(2) exact**
```sql
SELECT account,
sum(coalesce(debit, 0::DECIMAL(2))) AS total_debit,
sum(coalesce(credit, 0::DECIMAL(2))) AS total_credit,
sum(coalesce(debit, 0)) AS mixed_types
FROM ledger
GROUP BY account
ORDER BY account;
```
Result:
| account (STRING) | total_debit (DECIMAL(2)) | total_credit (DECIMAL(2)) | mixed_types (DECIMAL(2)) |
|---|---|---|---|
| Bank | 300.00 | 0.00 | 300.00 |
| Cash | 5000.00 | 750.25 | 5000 |
| Rent | 1200.50 | 0.00 | 1200.50 |
(3 rows)
In the `Cash` row, `mixed_types` is `5000` (a `DOUBLE`), not `5000.00`: one row added the `DECIMAL(2)` value `5000.00` and the other row added the whole number `0`.
**Example: coalesce needs at least one value** (shows an error)
```sql
SELECT coalesce();
```
Result:
Error: `line 1, column 8: function 'coalesce' takes 1 to 64 argument(s), got 0`
## nullif
Syntax:
```sql
nullif(value, other)
```
Gives `NULL` when `value` equals `other`. Otherwise it gives `value`. It is a short way to write `CASE WHEN value = other THEN NULL ELSE value END`.
| Argument | Type | Meaning |
|---|---|---|
| `value` | any | the value to give back |
| `other` | any | when `value` equals this, the answer is `NULL` |
- **Returns:** `value` (with its type) or `NULL`.
- **NULL:** `nullif(NULL, x)` is `NULL`. `nullif(x, NULL)` is `x`.
- **Notes:**
- Equal means the same as `=`: `nullif(5, 5.0)` and `nullif(5, '5')` are `NULL`. Text is compared exactly (`'a'` is not `'A'`).
- Values that cannot be compared give back `value`, without an error.
- Division by zero is an error, as in PostgreSQL, so `x / nullif(y, 0)` is the way to get `NULL` for it. `nullif` is also useful for `''` or a "no data" marker like `-1`.
**Example**
```sql
SELECT nullif(5, 5) AS a, nullif(10, 0) AS b, nullif(5, '5') AS c, nullif('a', 'A') AS d, nullif(NULL, 1) AS e;
```
Result:
| a (STRING) | b (LONG) | c (STRING) | d (STRING) | e (STRING) |
|---|---|---|---|---|
| NULL | 10 | NULL | a | NULL |
(1 row)
**Example: Empty notes count as no note**
```sql
SELECT id, note,
nullif(note, '') AS clean_note,
coalesce(nullif(note, ''), '(no note)') AS shown
FROM ledger
ORDER BY id;
```
Result:
| id (INT) | note (STRING) | clean_note (STRING) | shown (STRING) |
|---|---|---|---|
| 1 | Opening balance | Opening balance | Opening balance |
| 2 | NULL | NULL | (no note) |
| 3 | Shop sale | Shop sale | Shop sale |
| 4 | | NULL | (no note) |
(4 rows)
**Example: Average only over rows that really have a value** (sample data: sample)
```sql
SELECT symbol,
round(avg(quantity), 1) AS avg_all,
round(avg(nullif(quantity, 1000)), 1) AS avg_without_1000_lots,
count(nullif(quantity, 1000)) AS trades_not_1000
FROM trade_log
GROUP BY symbol
ORDER BY symbol;
```
Result:
| symbol (SYMBOL) | avg_all (DOUBLE) | avg_without_1000_lots (DOUBLE) | trades_not_1000 (LONG) |
|---|---|---|---|
| CITY | 220 | 220 | 11 |
| COAST | 279.1 | 118.9 | 9 |
| TELCO | 461.7 | 192.5 | 4 |
| VOLT | 100 | 100 | 4 |
(4 rows)
## NULL rules
Syntax:
```sql
value IS NULL value IS NOT NULL
coalesce(value, default) nullif(value, other)
```
`NULL` means "no value". Almost everything that touches `NULL` gives `NULL`, because the answer is not known. Use [`IS NULL`](where.md) to test for it: `x = NULL` is never true.
| Where | What `NULL` does | Example | Result |
|---|---|---|---|
| `+ - * / %` | gives `NULL` | `NULL + 1` | `NULL` |
| `= <> < > <= >=` | gives `NULL` (not true, not false) | `NULL = NULL` | `NULL` |
| `IS NULL`, `IS NOT NULL` | the only safe test | `NULL IS NULL` | `true` |
| `AND` | `false` wins, else `NULL` | `FALSE AND NULL`, `TRUE AND NULL` | `false`, `NULL` |
| `OR` | `true` wins, else `NULL` | `TRUE OR NULL`, `FALSE OR NULL` | `true`, `NULL` |
| `NOT` | gives `NULL` | `NOT NULL` | `NULL` |
| `IN (...)` | `NULL` when nothing matched and the list has a `NULL` | `3 IN (1, NULL)` | `NULL` |
| `BETWEEN`, `LIKE` | gives `NULL` | `NULL LIKE 'a%'` | `NULL` |
| `WHERE`, `HAVING`, `CASE WHEN` | `NULL` counts as not true: the row is left out, the next `WHEN` is tried | | |
| `\|\|` | gives `NULL`; `concat` skips `NULL` instead | `'a' \|\| NULL` | `NULL` |
| most functions | `NULL` in, `NULL` out | `abs(NULL)` | `NULL` |
| `coalesce`, `greatest`, `least`, `concat`, `concat_ws` | skip `NULL` values | `coalesce(NULL, 2)` | `2` |
| aggregates (`sum`, `avg`, `min`, `count(x)`, ...) | ignore `NULL` rows; `count(*)` counts every row | | |
| `ORDER BY` | `NULL` counts as the biggest value: last with `ASC`, first with `DESC` (change with `NULLS FIRST` / `NULLS LAST`) | | |
| `CAST(NULL AS type)` | stays `NULL` | `NULL::INT` | `NULL` |
- **Notes:** `3 NOT IN (1, NULL)` is `NULL`, not `true`. A `NOT IN` list with a `NULL` in it never keeps a row. See also [aggregates](functions-aggregate.md#count-star).
**Example**
```sql
SELECT NULL + 1 AS plus, NULL = NULL AS equal, NULL IS NULL AS is_null,
FALSE AND NULL AS and_false, TRUE OR NULL AS or_true,
3 IN (1, NULL) AS in_list, 3 NOT IN (1, NULL) AS not_in_list, 'a' || NULL AS joined;
```
Result:
| plus (STRING) | equal (STRING) | is_null (BOOL) | and_false (BOOL) | or_true (BOOL) | in_list (STRING) | not_in_list (STRING) | joined (STRING) |
|---|---|---|---|---|---|---|---|
| NULL | NULL | true | false | true | NULL | NULL | NULL |
(1 row)
**Example: = NULL finds nothing; IS NULL finds the rows**
```sql
SELECT (SELECT count(*) FROM app_users WHERE phone = NULL) AS with_equals_null,
(SELECT count(*) FROM app_users WHERE phone IS NULL) AS with_is_null;
```
Result:
| with_equals_null (LONG) | with_is_null (LONG) |
|---|---|
| 0 | 2 |
(1 row)
**Example: count(*) counts rows, count(x) skips NULL**
```sql
SELECT count(*) AS users, count(phone) AS with_phone, count(email) AS with_email, count(plan) AS with_plan
FROM app_users;
```
Result:
| users (LONG) | with_phone (LONG) | with_email (LONG) | with_plan (LONG) |
|---|---|---|---|
| 4 | 2 | 2 | 3 |
(1 row)