Before you use this reference
This page is a lookup, not a tutorial. The explanations live in SQL Querying and Aggregations, MergeTree and Table Design, and Building Staging and Data Marts. Syntax is ClickHouse.
SELECT skeleton#
SELECT col, agg(col) AS aliasFROM db.tableWHERE row_conditionGROUP BY colHAVING group_conditionORDER BY alias DESCLIMIT 100;Evaluation order: FROM → WHERE → GROUP BY → HAVING → SELECT →
ORDER BY → LIMIT.
WHERE operators#
a = b a != ba > b a >= b a BETWEEN x AND ya IN (1, 2, 3)s LIKE 'prefix%'col IS NULL col IS NOT NULLcond1 AND cond2 cond1 OR (cond2 AND cond3)GROUP BY and HAVING#
-- WHERE filters rows before grouping-- HAVING filters groups after grouping (can use aggregates)SELECT customer_id, sum(amount) AS totalFROM staging.ordersWHERE status = 'completed'GROUP BY customer_idHAVING total > 1000;
-- group by an expressionGROUP BY toStartOfMonth(created_at)Aggregate functions#
count() -- rowscount(col) -- non-null values of colsum(col) avg(col) min(col) max(col)uniq(col) -- approximate distinct count (fast)uniqExact(col) -- exact distinct count
-- conditional forms: normal args + a trailing conditioncountIf(amount >= 1000)sumIf(amount, status = 'completed')avgIf(amount, status = 'completed')uniqIf(customer_id, segment = 'pro')JOIN types#
FROM fact AS fLEFT JOIN dim AS d ON f.key = d.key -- keep all fact rows; dim cols NULL if no matchINNER JOIN dim AS d ON f.key = d.key -- keep only rows that match on both sidesAlias every table; qualify every column (f.amount, d.segment).
CTEs (WITH)#
WITH step_one AS( SELECT customer_id, sum(amount) AS revenue FROM staging.orders GROUP BY customer_id),step_two AS( SELECT customer_id FROM step_one WHERE revenue > 1000)SELECT * FROM step_two;
-- a CTE can feed an insertWITH s AS ( SELECT ... )INSERT INTO mart.t (a, b)SELECT a, b FROM s;Creating databases and tables#
CREATE DATABASE IF NOT EXISTS analytics;
CREATE TABLE IF NOT EXISTS analytics.orders( order_id UInt64, customer_id UInt64, created_at DateTime, amount Float64, status String, loaded_at DateTime DEFAULT now())ENGINE = MergeTreePARTITION BY toYYYYMM(created_at) -- optional; coarse onlyORDER BY (created_at, order_id); -- sort key: lead with filter columnsEngines:
MergeTree keep every rowReplacingMergeTree(ver) latest ver per sort key wins (eventually)SummingMergeTree sum additive columns per sort key (eventually)ClickHouse data types#
UInt8 UInt16 UInt32 UInt64 non-negative integersInt32 Int64 signed integersFloat32 Float64 approximate decimalsDecimal(P, S) exact decimals (money)String textDate DateTime DateTime64(3) date / second / millisecond precisionNullable(T) T or NULLLowCardinality(String) few distinct string valuesClickHouse time functions#
now() -- current timestamptoday() -- current datetoStartOfDay(ts) toStartOfHour(ts)toStartOfMonth(ts) toStartOfWeek(ts)toStartOfInterval(ts, INTERVAL 30 MINUTE)toYYYYMM(ts) toYYYYMMDD(ts) -- partition keysts >= now() - INTERVAL 3 HOUR -- lookback windowclickhouse-connect client#
from clickhouse_connect import get_client
client = get_client(host="clickhouse", port=8123, username="default", password="password")| Call | Purpose |
|---|---|
client.command(sql) | DDL, INSERT ... SELECT |
client.query(sql) | Read rows into Python |
client.query_df(sql) | Read rows into a DataFrame |
client.insert(table, data, column_names) | Load Python sequences |
client.insert_df(table, df, column_names) | Load a DataFrame |
result.result_rows | List of row tuples |
result.result_columns | Values grouped by column |
result.result_columns[0][0] | Scalar from a one-cell result |
# pass values as parameters, never with f-stringsdf = client.query_df( "SELECT * FROM analytics.orders WHERE created_at > {since:DateTime}", parameters={"since": last_processed},)Incremental load skeleton#
# 1. watermark from the targetres = client.query("SELECT maxOrNull(created_at) FROM mart.customer_metrics")since = res.result_columns[0][0]
# 2. read a recomputed window (covers late data), or everything on first runsql = "SELECT * FROM staging.orders"params = {}if since is not None: sql += " WHERE created_at >= {since:DateTime}" params = {"since": since}
df = client.query_df(sql, parameters=params)if df.empty: return
# 3. idempotent write: replace the affected partition, then insert