docs/en/sql-reference/functions/conditional-functions.md
Conditionals always result to 0, 1 or NULL. So you can use conditional results directly like this:
SELECT left < right AS is_small
FROM LEFT_RIGHT
┌─is_small─┐
│ ᴺᵁᴸᴸ │
│ 1 │
│ 0 │
│ 0 │
│ ᴺᵁᴸᴸ │
└──────────┘
When NULL values are involved in conditionals, the result will also be NULL.
SELECT
NULL < 1,
2 < NULL,
NULL < NULL,
NULL = NULL
┌─less(NULL, 1)─┬─less(2, NULL)─┬─less(NULL, NULL)─┬─equals(NULL, NULL)─┐
│ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │
└───────────────┴───────────────┴──────────────────┴────────────────────┘
So you should construct your queries carefully if the types are Nullable.
The following example demonstrates this by failing to add equals condition to multiIf.
SELECT
left,
right,
multiIf(left < right, 'left is smaller', left > right, 'right is smaller', 'Both equal') AS faulty_result
FROM LEFT_RIGHT
┌─left─┬─right─┬─faulty_result────┐
│ ᴺᵁᴸᴸ │ 4 │ Both equal │
│ 1 │ 3 │ left is smaller │
│ 2 │ 2 │ Both equal │
│ 3 │ 1 │ right is smaller │
│ 4 │ ᴺᵁᴸᴸ │ Both equal │
└──────┴───────┴──────────────────┘
The CASE expression in ClickHouse provides conditional logic similar to the SQL CASE operator. It evaluates conditions and returns values based on the first matching condition.
ClickHouse supports two forms of CASE:
CASE WHEN ... THEN ... ELSE ... END
This form allows full flexibility and is internally implemented using the multiIf function. Each condition is evaluated independently, and expressions can include non-constant values.
SELECT
number,
CASE
WHEN number % 2 = 0 THEN number + 1
WHEN number % 2 = 1 THEN number * 10
ELSE number
END AS result
FROM system.numbers
WHERE number < 5;
-- is translated to
SELECT
number,
multiIf((number % 2) = 0, number + 1, (number % 2) = 1, number * 10, number) AS result
FROM system.numbers
WHERE number < 5
┌─number─┬─result─┐
│ 0 │ 1 │
│ 1 │ 10 │
│ 2 │ 3 │
│ 3 │ 30 │
│ 4 │ 5 │
└────────┴────────┘
5 rows in set. Elapsed: 0.002 sec.
CASE <expr> WHEN <val1> THEN ... WHEN <val2> THEN ... ELSE ... END
This more compact form is optimized for constant value matching and internally uses caseWithExpression().
For example, the following is valid:
SELECT
number,
CASE number
WHEN 0 THEN 100
WHEN 1 THEN 200
ELSE 0
END AS result
FROM system.numbers
WHERE number < 3;
-- is translated to
SELECT
number,
caseWithExpression(number, 0, 100, 1, 200, 0) AS result
FROM system.numbers
WHERE number < 3
┌─number─┬─result─┐
│ 0 │ 100 │
│ 1 │ 200 │
│ 2 │ 0 │
└────────┴────────┘
3 rows in set. Elapsed: 0.002 sec.
This form also does not require return expressions to be constants.
SELECT
number,
CASE number
WHEN 0 THEN number + 1
WHEN 1 THEN number * 10
ELSE number
END
FROM system.numbers
WHERE number < 3;
-- is translated to
SELECT
number,
caseWithExpression(number, 0, number + 1, 1, number * 10, number)
FROM system.numbers
WHERE number < 3
┌─number─┬─caseWithExpr⋯0), number)─┐
│ 0 │ 1 │
│ 1 │ 10 │
│ 2 │ 2 │
└────────┴──────────────────────────┘
3 rows in set. Elapsed: 0.001 sec.
ClickHouse determines the result type of a CASE expression (or its internal equivalent, such as multiIf) before evaluating any conditions. This is important when the return expressions differ in type, such as different timezones or numeric types.
For example, below all rows return the timestamp in the timezone of the first matched branch i.e. Asia/Kolkata
SELECT
number,
CASE
WHEN number = 0 THEN fromUnixTimestamp64Milli(0, 'Asia/Kolkata')
WHEN number = 1 THEN fromUnixTimestamp64Milli(0, 'America/Los_Angeles')
ELSE fromUnixTimestamp64Milli(0, 'UTC')
END AS tz
FROM system.numbers
WHERE number < 3;
-- is translated to
SELECT
number,
multiIf(number = 0, fromUnixTimestamp64Milli(0, 'Asia/Kolkata'), number = 1, fromUnixTimestamp64Milli(0, 'America/Los_Angeles'), fromUnixTimestamp64Milli(0, 'UTC')) AS tz
FROM system.numbers
WHERE number < 3
┌─number─┬──────────────────────tz─┐
│ 0 │ 1970-01-01 05:30:00.000 │
│ 1 │ 1970-01-01 05:30:00.000 │
│ 2 │ 1970-01-01 05:30:00.000 │
└────────┴─────────────────────────┘
3 rows in set. Elapsed: 0.011 sec.
Here, ClickHouse sees multiple DateTime64(3, <timezone>) return types. It infers the common type as DateTime64(3, 'Asia/Kolkata' as the first one it sees, implicitly casting other branches to this type.
This can be addressed by converting to a string to preserve intended timezone formatting:
SELECT
number,
multiIf(
number = 0, formatDateTime(fromUnixTimestamp64Milli(0), '%F %T', 'Asia/Kolkata'),
number = 1, formatDateTime(fromUnixTimestamp64Milli(0), '%F %T', 'America/Los_Angeles'),
formatDateTime(fromUnixTimestamp64Milli(0), '%F %T', 'UTC')
) AS tz
FROM system.numbers
WHERE number < 3;
-- is translated to
SELECT
number,
multiIf(number = 0, formatDateTime(fromUnixTimestamp64Milli(0), '%F %T', 'Asia/Kolkata'), number = 1, formatDateTime(fromUnixTimestamp64Milli(0), '%F %T', 'America/Los_Angeles'), formatDateTime(fromUnixTimestamp64Milli(0), '%F %T', 'UTC')) AS tz
FROM system.numbers
WHERE number < 3
┌─number─┬─tz──────────────────┐
│ 0 │ 1970-01-01 05:30:00 │
│ 1 │ 1969-12-31 16:00:00 │
│ 2 │ 1970-01-01 00:00:00 │
└────────┴─────────────────────┘
3 rows in set. Elapsed: 0.002 sec.