Skip to content
Finitizer

A CTE referenced more than once may be evaluated more than once

BigQuery does not promise to compute a WITH clause once.

A CTE used in three places may read its tables three times; check with a dry run.

Check one of your own queries for this

Paste a query and the analyzer estimates what it scans and flags the patterns it can detect statically. It runs in your browser.

What it is

A common table expression is a name for a query, not a temporary table. Each reference may be expanded and run on its own. The optimizer can reuse a result, but nothing guarantees it, so it is 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 can compound: a CTE reading a large table, referenced four times, may read that table four times. A dry run of the query shows whether it does.
  • 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 revenue
Cheaper
-- 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 and each reference reads it again, the first form bills 120 GiB ($0.73) against the second's 40 GiB ($0.24), for an identical result. A dry run of each shows which case you are in.

How to fix it

  1. 1Aggregate in a single pass where the query allows it.
  2. 2If the intermediate result is genuinely needed several times, write it to a temporary table and query that.
  3. 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.

See how Finitizer works