Querying with SQL
This domain tests ClickHouse's SQL dialect: filtering stages, aggregation and window idioms, join strictness and direction, cluster-aware constructs, and per-query settings. Expect questions that hand you a query and ask why it is slow, wrong, or non-deterministic.
Filtering and reading stage
PREWHEREapplies a cheap filter before reading the remaining columns;optimize_move_to_prewheredoes this automatically, so hand-written PREWHERE is a tuning tool, not a default.FINALforces merge-on-read for Replacing/Collapsing tables - correct but expensive on wide scans.SAMPLE krequires the table to declareSAMPLE BY.
Aggregation idioms
uniq()is approximate (HyperLogLog) and memory-bounded;uniqExact()andcount(DISTINCT x)are exact and costly at high cardinality.argMax(col, ts),groupArray,quantile(0.5)/median,topKcover most "latest value" and percentile questions.LIMIT n BY colreturns the first n rows per group after ORDER BY - cheaper than a window function for top-N-per-group.WITH TOTALS,ROLLUP,CUBE, and standardOVER (PARTITION BY ... ORDER BY ...)windows are supported.
Joins
- Default
join_algorithm = hashbuilds the hash table from the right-hand table, so the smaller table belongs on the right - the opposite of many engines. join_use_nulls = 0by default: unmatched left rows receive type defaults, not NULL. Setjoin_use_nulls = 1for standard SQL semantics.- Strictness
ALLis the default;ANY,SEMI/ANTI, andASOF(time-series nearest-match) are available. - On a cluster,
GLOBAL JOIN/GLOBAL INship the right side once instead of rebuilding it per shard.
Worked example
Top 3 event types per user:
SELECT user_id, event, c FROM (SELECT user_id, event, count() AS c FROM events GROUP BY user_id, event) ORDER BY c DESC LIMIT 3 BY user_id
The inner aggregate reduces data first; LIMIT 3 BY then keeps the first three rows per user_id in the sorted stream. A window-function rewrite scans the same rows but materializes more state.
Settings and output
- Prefer the
SETTINGS max_execution_time = 10clause over session-levelSET, which leaks into later queries on the same connection. - Always specify
FORMATwhen piping results into tooling; verify index usage withEXPLAIN indexes = 1.
Traps that cost marks
SELECT *on wide tables andNOT INagainst Nullable columns (NULL makes the predicate never true).- Assuming unmatched joins yield NULL when
join_use_nullsis off. - Using
count(DISTINCT)on millions of distinct values.