SQL Quick Reference

A compact lookup for SELECT structure, joins, aggregates, CTEs, ClickHouse table syntax, time functions, and the clickhouse-connect client.

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#

SQL
SELECT   col, agg(col) AS aliasFROM     db.tableWHERE    row_conditionGROUP BY colHAVING   group_conditionORDER BY alias DESCLIMIT    100;

Evaluation order: FROMWHEREGROUP BYHAVINGSELECTORDER BYLIMIT.

WHERE operators#

SQL
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#

SQL
-- 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#

SQL
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#

SQL
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 sides

Alias every table; qualify every column (f.amount, d.segment).

CTEs (WITH)#

SQL
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#

SQL
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 columns

Engines:

Text
MergeTree                 keep every rowReplacingMergeTree(ver)   latest ver per sort key wins (eventually)SummingMergeTree          sum additive columns per sort key (eventually)

ClickHouse data types#

Text
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 values

ClickHouse time functions#

SQL
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 window

clickhouse-connect client#

Python
from clickhouse_connect import get_client
client = get_client(host="clickhouse", port=8123,                    username="default", password="password")
CallPurpose
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_rowsList of row tuples
result.result_columnsValues grouped by column
result.result_columns[0][0]Scalar from a one-cell result
Python
# 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#

Python
# 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

See also#