A CTE referenced more than once is evaluated more than once
BigQuery does not materialize WITH clauses. A CTE used in three places runs three times, and bills three times.
What it is
A common table expression is a name for a query, not a temporary table. Each reference is expanded and executed independently unless the optimizer happens to reuse the result — which is not guaranteed and not something to rely on.
Why it costs money
- The mental model most people bring is "compute once, use many". BigQuery does not promise that.
- It compounds: a CTE reading a large table, referenced four times, scans that table four times.
- Query length hides it. Nobody counts CTE references in a 300-line query.
Worked example
Expensive
WITH base AS (
SELECT * FROM `analytics.events` WHERE event_date = CURRENT_DATE()
)
SELECT (SELECT COUNT(*) FROM base) AS total,
(SELECT COUNT(DISTINCT user_id) FROM base) AS users,
(SELECT SUM(revenue) FROM base) AS revenueCheaper
-- One pass:
SELECT COUNT(*) AS total,
COUNT(DISTINCT user_id) AS users,
SUM(revenue) AS revenue
FROM `analytics.events`
WHERE event_date = CURRENT_DATE()The arithmetic. If the CTE scans 40 GiB, the first form can bill 120 GiB ($0.73) against the second's 40 GiB ($0.24) — for an identical result.
How to fix it
- 1Aggregate in a single pass where the query allows it.
- 2If the intermediate result is genuinely needed several times, write it to a temporary table and query that.
- 3For a repeated, expensive intermediate, a materialized view pays for itself quickly.
The analyzer detects this one. Paste your query into the BigQuery Query Cost Analyzer and it will point at the exact line.
Fixing one query is satisfying. Fixing the pattern is the win.
Finitizer finds every instance of this across your BigQuery job history, ranks them by what they actually cost, and keeps checking after you have fixed them.