Live demo · Federated SQL

Three systems.
One query.

Join orders in Snowflake, sessions in BigQuery and a product catalog in Parquet with one ChalkSQL query. Each source gets the filters and columns it can handle, sends back only what the query needs, and Chalk joins the rest.

Ask across systems

Fernwood Supply, a fictional outdoor-gear retailer, keeps orders in Snowflake, web sessions in BigQuery and its product catalog as Parquet on GCS. Pick a question to see the query, what each source was asked to do, how many rows it sent back and the answer. What you see first is a recorded run; Run live sends the same query to the same three systems again.

Which categories made the most money in the mountain region over the last 30 days?

Recorded run · 1,503 ms end to end

One ChalkSQL query2 sources
SELECT p.category,
       COUNT(*) AS orders,
       SUM(CAST(o.QUANTITY AS DOUBLE) * o.UNIT_PRICE_USD) AS revenue_usd,
       SUM(CAST(o.QUANTITY AS DOUBLE) * (o.UNIT_PRICE_USD - p.unit_cost_usd)) AS margin_usd
FROM andymoreland_snowflake.SYNTHETIC_EXAMPLES.FERNWOOD_ORDERS AS o
JOIN read_parquet('gs://chalk-staging-test-data/demos/cross-source-sql/products.parquet') AS p
  ON o.SKU = p.sku
WHERE o.ORDER_TS >= TIMESTAMPTZ '2026-09-10 00:00:00+00'
  AND o.REGION = 'mountain'
GROUP BY p.category
ORDER BY revenue_usd DESC
What each source didpushed down
SnowflakeWarehouse
896 ms in the source
Sent back 313 of 3,996 rows
Filter
ORDER_TS >= TIMESTAMP '2026-09-10 00:00:00' AND REGION = 'mountain'
Columns
SKU, QUANTITY, UNIT_PRICE_USD, ORDER_TS, REGION
Parquet on GCSData lake
Sent back 28 of 28 rows
Filter
none pushed: every row is read
Columns
sku, category, unit_cost_usd
What Chalk didon the rows that came back
  1. 1Inner hash join on o.SKU = p.sku
  2. 2Aggregate by p.category
  3. 3Sort
Results · 7 rows
categoryordersrevenue_usdmargin_usd
Tents50$16,955$9,917
Apparel72$14,203$10,182
Footwear42$9,687$6,917
Cookware84$8,576$6,055
Climbing39$7,485$4,905
Sleep15$5,208$3,596
Packs11$1,697$1,209

How a federated query runs

Moving whole tables to one place before you can join them is slow and expensive. A federated query moves the question instead, and only the rows that answer it travel.

Every source is a table

Snowflake, BigQuery, Postgres and the rest of your integrations appear as <integration>.<schema>.<table>, and files on S3 or GCS are one read_parquet() away. Nothing is copied first.

Each source does its share

The planner splits the query by source and hands each warehouse the filters and columns it can evaluate, as SQL in its own dialect. Parquet scans read only the columns the query uses.

Chalk does the rest

The rows that come back meet in Chalk's vectorized engine: joins across systems, time-window conditions, aggregates and sorts, in one plan.

Getting started

Query the integrations you already have

Any data source connected to your Chalk environment is already a ChalkSQL table. Run a query from the SQL editor in the dashboard, from Python with run_sql, or from the chalk CLI, and put EXPLAIN in front of it to see what each source will be asked to do.

More on federated queries
-- Marketing sessions in BigQuery, orders in Snowflake, the catalog as Parquet on GCS.
SELECT p.product_name, p.category,
       COUNT(*) AS orders,
       SUM(CAST(o.QUANTITY AS DOUBLE) * o.UNIT_PRICE_USD) AS revenue_usd
FROM (
  SELECT customer_id, session_start
  FROM marketing_bq.analytics.web_sessions
  WHERE campaign = 'crag-season'
) AS s
JOIN finance_snowflake.SALES.ORDERS AS o
  ON o.CUSTOMER_ID = s.customer_id
JOIN read_parquet('gs://catalog-exports/products.parquet') AS p
  ON p.sku = o.SKU
WHERE o.ORDER_TS >= s.session_start
  AND o.ORDER_TS < s.session_start + INTERVAL '2 hours'
GROUP BY p.product_name, p.category
ORDER BY revenue_usd DESC
LIMIT 5

Writing queries that push down

The less each source sends back, the faster the join. A few habits keep the work in the warehouse.

Filters on a source's own columns

=, <>, <, <=, >, >=, IS NULL and IS NOT NULL against a literal, combined with AND and OR, become a WHERE clause in the source's SQL. Spell short value lists out with OR.

Filter in a subquery when joins compare ranges

When a join also has a time window, write each source's filters in a subquery over that source. They are pushed into the source's SQL instead of applied after the join.

Select what you use

Column lists are pushed to every source, Parquet included. A query that names six columns moves six columns over the network, however wide the table is.

No copies to keep in sync

Query data where it lives. There is no pipeline to build before a question can span two systems, and no stale replica to reconcile afterwards.

Each source's permissions apply

Every source is read through its own integration and credentials, so a query can only see what those integrations are allowed to see.

Measured, not guessed

Each run records the SQL every source received, how many rows it returned and how long it took, so you can see exactly where the time went.

Ask the question, not the pipeline

Bring the warehouses and lakes you already run. Talk to an engineer about querying them together with ChalkSQL.