Back to Clickhouse

Conditional Functions

docs/en/sql-reference/functions/conditional-functions.md

26.4.1.1-new7.7 KB
Original Source

Conditional functions

Overview {#overview}

Using Conditional Results Directly {#using-conditional-results-directly}

Conditionals always result to 0, 1 or NULL. So you can use conditional results directly like this:

sql
SELECT left < right AS is_small
FROM LEFT_RIGHT

┌─is_small─┐
│     ᴺᵁᴸᴸ │
│        1 │
│        0 │
│        0 │
│     ᴺᵁᴸᴸ │
└──────────┘

NULL Values in Conditionals {#null-values-in-conditionals}

When NULL values are involved in conditionals, the result will also be NULL.

sql
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.

sql
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────┐
│ ᴺᵁᴸᴸ │     4Both equal       │
│    13left is smaller  │
│    22Both equal       │
│    31right is smaller │
│    4 │  ᴺᵁᴸᴸ │ Both equal       │
└──────┴───────┴──────────────────┘

CASE statement {#case-statement}

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:

  1. 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.

sql
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─┐
│      01 │
│      110 │
│      23 │
│      330 │
│      45 │
└────────┴────────┘

5 rows in set. Elapsed: 0.002 sec.
  1. 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:

sql
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─┐
│      0100 │
│      1200 │
│      20 │
└────────┴────────┘

3 rows in set. Elapsed: 0.002 sec.

This form also does not require return expressions to be constants.

sql
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)─┐
│      01 │
│      110 │
│      22 │
└────────┴──────────────────────────┘

3 rows in set. Elapsed: 0.001 sec.

Caveats {#caveats}

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.

  • The result type is selected based on the largest compatible type among all branches.
  • Once this type is selected, all other branches are implicitly cast to it - even if their logic would never be executed at runtime.
  • For types like DateTime64, where the timezone is part of the type signature, this can lead to surprising behavior: the first encountered timezone may be used for all branches, even when other branches specify different timezones.

For example, below all rows return the timestamp in the timezone of the first matched branch i.e. Asia/Kolkata

sql
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─┐
│      01970-01-01 05:30:00.000 │
│      11970-01-01 05:30:00.000 │
│      21970-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:

sql
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──────────────────┐
│      01970-01-01 05:30:00 │
│      11969-12-31 16:00:00 │
│      21970-01-01 00:00:00 │
└────────┴─────────────────────┘

3 rows in set. Elapsed: 0.002 sec.
<!-- The inner content of the tags below are replaced at doc framework build time with docs generated from system.functions. Please do not modify or remove the tags. See: https://github.com/ClickHouse/clickhouse-docs/blob/main/contribute/autogenerated-documentation-from-source.md --> <!--AUTOGENERATED_START--> <!--AUTOGENERATED_END-->