Blog· Tested on ClickHouse 26.4

Why does 1 <= x < 3 not mean what Python says?

I asked for Python-style chained comparisons so half-open ranges read naturally. In ClickHouse today the chain parses, runs, and quietly returns the wrong answer.

Source: github.com

I asked for Python-style chained comparisons, so that a half-open range reads as a <= x < b and means a <= x AND x < b with x evaluated once. The motivation is the time-range filter: BETWEEN is closed on both ends, so it counts the row stamped exactly at the window’s end twice (once in each adjacent window), and everyone ends up writing the column name twice with >= and <. My example was five conditions across three columns collapsing into one chain.

The issue got the feature label, was assigned to a contributor in June 2022, and then went quiet. In July 2026 the repository’s automation unassigned him after more than a year of inactivity. No PR is linked; the issue is open.

What makes it more than a nicety is that ClickHouse accepts the chained form and does not do what Python does. It parses a <= x < b as (a <= x) < b, compares a 0/1 result with b, and carries on. Checked on 26.4:

SELECT 3 <= 2 < 3 AS chained, (3 <= 2) AND (2 < 3) AS python_meaning;
-- chained = 1, python_meaning = 0
SELECT 5 > 3 > 1;   -- 0, Python says True

On numeric columns this silently returns wrong rows: WHERE 1 <= duration_ms < 3 keeps every row, because both 0 and 1 are less than 3. With a DateTime and string bounds the type check happens to stop you with a TYPE_MISMATCH. Whether the fix is real chaining or just rejecting a comparison of a comparison, the status quo is a trap. Until then, write both conditions out. More small tools of this kind are in the lesser-known functions tutorial.

feature-requestsql-syntaxoperators

Discussion

Comments live in GitHub Discussions. Sign in with GitHub to join in.