BigQuery FinOps Query Pack

How do I attribute BigQuery cost to a team or pipeline?

Break query spend down by job label — the only allocation dimension BigQuery gives you.

Free to run — INFORMATION_SCHEMA scans no billable bytesChecked 4 Aug 2026

JOBS and TABLE_STORAGE are region-scoped.

-- Spend by job label. Labels must be set by the client that submits the job;
-- unlabelled jobs land in the '(none)' bucket, which is the number to watch.
SELECT
  IFNULL(label.key, '(none)')                               AS label_key,
  IFNULL(label.value, '(none)')                             AS label_value,
  COUNT(*)                                                  AS jobs,
  ROUND(SUM(total_bytes_billed) / POW(1024, 4) * 6.25, 2)   AS est_on_demand_usd,
  ROUND(SUM(total_slot_ms) / 1000 / 3600, 1)                AS slot_hours
FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
LEFT JOIN UNNEST(labels) AS label
WHERE creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
  AND job_type = 'QUERY'
  AND state = 'DONE'
GROUP BY label_key, label_value
ORDER BY est_on_demand_usd DESC;

What it returns

label_key / label_value
The job label pair. "(none)" is unlabelled spend.

How to read it

  • The "(none)" row is the point of this query. If it is most of your spend, allocation is not working and no amount of downstream reporting will fix it.
  • A job carrying two labels appears in two rows, so the total across rows exceeds real spend. Never sum this column — read it one label key at a time.
  • Labels are set at submission. dbt, Dataform, Airflow, and Looker can all set them; the ad-hoc console cannot, which is why interactive work is usually unlabelled.

Take it further

Run this once, or have it run every day.

Finitizer evaluates this class of question continuously against your live BigQuery estate, tracks how each number moves between runs, and turns findings into assigned tasks. Read-only and keyless.