Retrieve rows from tables across your Chalk environment.
The SELECT statement retrieves rows from tables in the Chalk catalog, from
configured data sources, and from table-valued functions. It is the primary
statement in Chalk SQL: every query reads data through a SELECT (including
the query embedded in COPY ... TO and
EXPLAIN).
-- Select all columns from a data source table
SELECT * FROM "my_postgres.public.users";
-- Aggregate transactions per merchant
SELECT merchant_id, count(*) AS txn_count, sum(amount) AS total
FROM "my_bigquery.my_dataset.transactions"
GROUP BY merchant_id
ORDER BY total DESC
LIMIT 10;
-- Join tables that live in different data sources
SELECT u.user_id, sum(t.amount) AS user_spending
FROM "my_postgres.public.users" u
LEFT JOIN "my_bigquery.my_dataset.transactions" t ON u.user_id = t.user_id
GROUP BY u.user_id;
-- Use a CTE and a window function to pick each user's latest transaction
WITH ranked AS (
SELECT
user_id,
amount,
row_number() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM "my_bigquery.my_dataset.transactions"
)
SELECT user_id, amount FROM ranked WHERE rn = 1;
-- Query historical feature values from the offline store
SELECT pkey, value, observed_at
FROM "chalk.historical_values.user.account_balance"
WHERE observed_at > TIMESTAMP '2026-08-25 00:00:00';Clauses appear in the following order. Every clause other than the SELECT
list is optional.
[ WITH cte_name [ (column_alias, ...) ] AS ( select ) [, ...] ]
SELECT [ DISTINCT | DISTINCT ON (expression, ...) ] expression [ AS alias ] [, ...]
[ FROM source [, ...] ]
[ [ join_type ] JOIN source [ ON condition | USING (column, ...) ] ]
[ WHERE condition ]
[ GROUP BY expression [, ...] ]
[ HAVING condition ]
[ QUALIFY condition ]
[ ORDER BY expression [ ASC | DESC ] [ NULLS FIRST | NULLS LAST ] [, ...] ]
[ LIMIT count [ OFFSET count ] ]Two or more SELECT statements can also be combined with
set operations (UNION, INTERSECT, EXCEPT).
Common table expressions (CTEs) name a subquery so it can be referenced like a
table in the rest of the statement. CTEs may declare column aliases and may
reference earlier CTEs in the same WITH list.
WITH spend(user_id, total) AS (
SELECT user_id, sum(amount)
FROM "my_bigquery.my_dataset.transactions"
GROUP BY user_id
)
SELECT u.user_id, s.total
FROM "my_postgres.public.users" u
JOIN spend s ON u.user_id = s.user_id;Not currently supported:
WITH RECURSIVEMATERIALIZED CTEsThe SELECT list contains any supported
expression or function, optionally renamed with AS. An
alias defined earlier in the SELECT list can be referenced by later
expressions in the same list (a “lateral” column alias):
SELECT amount * 100 AS cents, cents / 3 AS thirds
FROM "my_bigquery.my_dataset.transactions";DISTINCT deduplicates the result rows, and DISTINCT ON (expressions) keeps
one row per distinct value of the given expressions:
-- One row per user
SELECT DISTINCT ON (user_id) user_id, amount
FROM "my_bigquery.my_dataset.transactions"
ORDER BY user_id, created_at DESC;The star expression * selects all columns of the source. Restrictions:
SELECT * cannot be combined with GROUP BY or aggregate functions;
list the grouped columns explicitly instead.* EXCLUDE (...) and * REPLACE (...) are not supported.* cannot appear inside another expression (e.g. count(*) is supported,
but arbitrary functions of * are not).The FROM clause names the sources rows are read from. A source can be:
A catalog or data source table, referenced with a quoted fully-qualified
name: "catalog.schema.table". This includes data source tables (e.g.
"my_postgres.public.users") and Chalk catalog views such as
"chalk.online_store.keys" or "chalk.historical_values.user". See
Chalk Catalog Components.
A table-valued function, such as read_parquet(...),
query_values_from_operation_ids(...), or get_dataset_givens(...). See the
table-valued function reference.
A subquery, wrapped in parentheses and optionally aliased.
A VALUES list, for inline literal rows:
SELECT * FROM (VALUES (1, 'ada'), (2, 'grace')) AS t(id, name);
A file path or glob, which reads files directly:
SELECT * FROM 's3://my-bucket/events/*.parquet';
A SELECT without a FROM clause evaluates its expressions over a single row
(SELECT 1 + 1).
File-backed scans (direct file paths and read_parquet(...)) support
percentage-based sampling with the SYSTEM and BERNOULLI methods:
SELECT * FROM 's3://my-bucket/events/*.parquet' TABLESAMPLE SYSTEM (10 PERCENT);Not currently supported: sampling catalog or data source tables, RESERVOIR
sampling, absolute row-count samples, sampling subqueries or joins, and the
USING SAMPLE clause.
Chalk SQL supports joining tables, including tables that live in different data sources, with the following join types:
[INNER] JOINLEFT [OUTER] JOINRIGHT [OUTER] JOINFULL [OUTER] JOINCROSS JOINASOF JOIN and ASOF LEFT JOIN, for matching each row to the nearest
preceding row by an inequality condition (commonly a timestamp)Join conditions can be written with ON <condition> or USING (columns).
Inner joins also accept non-equality ON conditions.
-- Match each transaction to the feature value observed at or before it
SELECT t.user_id, t.amount, f.value AS balance_at_txn
FROM "my_bigquery.my_dataset.transactions" t
ASOF LEFT JOIN "chalk.historical_values.user.account_balance" f
ON t.user_id = f.pkey AND t.created_at >= f.observed_at;Not currently supported: NATURAL joins, POSITIONAL joins, and
SEMI/ANTI joins.
Filters rows with any supported boolean
expression, including subqueries with IN and EXISTS.
Where possible, Chalk SQL pushes filters down into the underlying data source
so that less data is scanned; pushdown is best-effort and does not change
results.
SELECT * FROM "my_postgres.public.users"
WHERE signup_date > DATE '2026-01-01'
AND country IN ('US', 'CA');Groups rows by one or more expressions for use with
aggregate functions. HAVING filters the grouped rows and
requires a GROUP BY clause or an aggregate in the SELECT list.
SELECT date_trunc('day', created_at) AS day, count(*) AS txns
FROM "my_bigquery.my_dataset.transactions"
GROUP BY date_trunc('day', created_at)
HAVING count(*) > 100;GROUPING SETS, ROLLUP, and CUBE are supported for aggregating across
several groupings in one query:
-- Per-country counts plus a grand-total row (country IS NULL)
SELECT country, count(*) FROM "my_bigquery.my_dataset.transactions"
GROUP BY ROLLUP (country);Not currently supported: GROUP BY ALL.
Window functions compute a value over a window of rows related to the current
row, using OVER (PARTITION BY ... ORDER BY ... [frame]). ROWS frames with
literal bounds are supported (e.g. ROWS BETWEEN 1 PRECEDING AND CURRENT
ROW).
The QUALIFY clause filters on window function results, the way HAVING
filters on aggregate results. A query must contain at least one window
function to use QUALIFY:
SELECT user_id, amount,
row_number() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM "my_bigquery.my_dataset.transactions"
QUALIFY rn <= 3;Not currently supported in window functions: the FILTER clause, frame
EXCLUDE options, non-literal frame bounds, and RANGE frames with offset
bounds (the default frame implied by ORDER BY works).
Sorts the result by one or more expressions. Each sort key accepts ASC
(default) or DESC, and NULLS FIRST / NULLS LAST. Sort keys can be
arbitrary expressions (including expressions not in the SELECT list),
positional references to SELECT list items (ORDER BY 2), or ORDER BY
ALL to sort by every selected column left to right.
LIMIT restricts the number of returned rows and OFFSET skips rows before
returning. Both must be literal integers.
SELECT * FROM "my_postgres.public.users" ORDER BY user_id LIMIT 100 OFFSET 200;Two or more SELECT statements can be combined with:
UNION (deduplicates) and UNION ALL (keeps duplicates)INTERSECTEXCEPTAn ORDER BY following a set operation may only reference output column names
or positions. To apply a LIMIT (or sort by a computed expression), wrap the
set operation in a subquery:
SELECT * FROM (
SELECT user_id FROM "my_postgres.public.users"
UNION
SELECT user_id FROM "my_postgres.public.archived_users"
)
ORDER BY user_id
LIMIT 50;Subqueries are supported in the FROM clause, as scalar expressions, and in
IN / EXISTS predicates, including correlated forms:
SELECT * FROM "my_postgres.public.users" u
WHERE EXISTS (
SELECT 1 FROM "my_bigquery.my_dataset.transactions" t
WHERE t.user_id = u.user_id AND t.amount > 10000
);Some constructs are not currently supported inside subqueries, including
GROUP BY within an EXISTS subquery, DISTINCT in correlated scalar
subqueries, and ARRAY(SELECT ...).