BigQuery INFORMATION_SCHEMA Cost Attribution: Stop the Guessing
Your BigQuery bill is growing but you can't pinpoint why. INFORMATION_SCHEMA has the answer — here's how to use it to attribute costs by user, model, and team before it's too late.
BigQuery INFORMATION_SCHEMA cost attribution is the practice of using BigQuery's built-in metadata views — primarily INFORMATION_SCHEMA.JOBS — to identify exactly which queries, users, dbt models, and scheduled jobs are responsible for your compute bill. Done properly, it replaces end-of-month invoice shock with proactive, team-level cost visibility that engineering leads can actually act on.
Without it, you are making cost decisions from a billing dashboard that tells you the total but never the cause. And in a fast-growing startup running dozens of dbt models, scheduled queries, and BI tool refreshes simultaneously, that is not a manageable position.
Why Your BigQuery Bill Is Growing and You Don't Know Why
This is the pattern we see most often with early-stage companies that have reached meaningful data scale: the bill is climbing, the team is growing, but nobody can tell you which query — or which team — is responsible.
Most BigQuery cost problems surface the wrong way: a surprise line item in last month's invoice, a screenshot of a spike forwarded by a team lead, a question from finance that nobody can answer quickly. By the time the conversation starts, the query that caused it ran two weeks ago.
The reason this happens is structural. BigQuery's serverless architecture and automatic scaling deliver unmatched analytical performance, but that same flexibility can lead to unexpected cost spikes when teams optimise for speed without understanding the underlying pricing model. And the pattern is consistent: costs often spiral when teams treat BigQuery as "pay-per-query" without considering slot utilisation, data scanning patterns, and storage lifecycle management.
The fix is not a new billing alert. The fix is cost attribution — knowing precisely who ran what, against which table, at what scan volume, and on what schedule. INFORMATION_SCHEMA.JOBS gives you all of this, and most teams are not using it.
What Makes Cost Attribution Different From Cost Optimisation
Optimisation (fixing partitioning, removing SELECT *, adding partition filters) addresses the how of expensive queries. Attribution addresses the who and what. You cannot prioritise optimisation work without attribution — you are just guessing at which of your fifty dbt models to fix first.
Attribution is also the layer that makes cost governance scalable. Once you can charge costs back to a team, a product surface, or a dbt tag, you create accountability without having to manually audit every query yourself.

📺 Watch: Monitor Snowflake query history, execution plan and query profile
How INFORMATION_SCHEMA.JOBS Actually Works
The INFORMATION_SCHEMA.JOBS view contains near real-time metadata about all BigQuery jobs in the current project. That metadata includes the query text itself, the user or service account that ran it, bytes billed, start and end time, and the job type.
The underlying data is partitioned by the creation_time column and clustered by project_id and user_email — which means querying it is cheap as long as you filter on creation_time first. Teams that query INFORMATION_SCHEMA.JOBS without a creation_time filter are ironically generating cost to investigate cost. Always scope your window.
You can query the INFORMATION_SCHEMA.JOBS_BY_* views to get detailed information about your queries, including their costs. The three scopes you will use most are:
JOBS_BY_PROJECT— everything in a single project. This is where most startups start.JOBS_BY_FOLDER— useful once you have multiple projects organised under a GCP folder (common from Series A onwards).JOBS_BY_ORGANIZATION— full organisation view, requires elevated IAM.
When you query INFORMATION_SCHEMA.JOBS to find a summary cost of query jobs, exclude the SCRIPT statement type — otherwise some values might be counted twice. The SCRIPT row includes summary values for all child jobs that were executed as part of that job. This is a subtle gotcha that inflates cost figures for teams running dbt operations or stored procedures through scheduled queries.
For stability, explicitly list columns in your information schema queries instead of using a wildcard (SELECT *). Explicitly listing columns prevents queries from breaking if the underlying schema changes.
The Four Attribution Dimensions That Matter
Once you have access to INFORMATION_SCHEMA.JOBS, you want to slice costs across four dimensions:
1. By user or service account — user_email tells you whether it is a human analyst, a BI tool service account, a dbt service account, or a scheduled query runner. A pattern we see repeatedly: a single analyst running exploratory queries against a large raw table — without partition filters — responsible for 40–60% of a project's monthly bill.
2. By job type and statement type — job_type (QUERY, LOAD, EXTRACT, COPY) and statement_type (SELECT, CREATE TABLE AS SELECT, INSERT, MERGE) tell you whether your cost is compute-driven or movement-driven. MERGE statements against large tables are a common hidden cost in dbt incremental models.
3. By query label — BigQuery allows you to attach labels to jobs at query time. In dbt, you can set job_labels in your profiles.yml or dbt_project.yml to tag every query with the model name, dbt invocation ID, or team name. This is the mechanism that turns raw job data into proper cost attribution. Without labels, you are left reverse-engineering cost from query text pattern matching — which works, but is brittle.
4. By bytes billed over time — trending total_bytes_billed by day and job origin tells you whether a cost spike is a one-off (someone ran a bad ad-hoc query) or structural (a new dbt model added last week is scanning a full table on every run).
The SQL You Actually Need
Here is the attribution query we use as a starting point with clients. It surfaces the top cost contributors by user over a rolling 30-day window, excluding scripts to avoid double-counting:
SELECT
user_email,
SUM(total_bytes_billed) / POW(1024, 4) AS total_tib_billed,
ROUND(
SUM(total_bytes_billed) / POW(1024, 4) * 6.25,
2
) AS estimated_cost_usd,
COUNT(*) AS job_count,
MAX(creation_time) AS last_job_at
FROM
`region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE
creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND job_type = 'QUERY'
AND statement_type != 'SCRIPT'
GROUP BY
user_email
ORDER BY
estimated_cost_usd DESC
LIMIT 20;
Note the $6.25 per TiB rate: BigQuery on-demand pricing charges $6.25 per TiB of data processed in many regions, including the US multi-region. Adjust for your region and edition.
To attribute costs to dbt models specifically, join against query labels:
SELECT
JSON_VALUE(labels, '$.dbt_model') AS dbt_model,
SUM(total_bytes_billed) / POW(1024, 4) AS total_tib_billed,
ROUND(
SUM(total_bytes_billed) / POW(1024, 4) * 6.25,
2
) AS estimated_cost_usd,
COUNT(*) AS run_count
FROM
`region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT,
UNNEST(labels) AS label
WHERE
creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND job_type = 'QUERY'
AND statement_type != 'SCRIPT'
GROUP BY
dbt_model
ORDER BY
estimated_cost_usd DESC;
This only works if you have configured dbt job labels. If you have not, this is the first thing to fix — without it, you cannot attribute cost to individual models without brittle string-matching against query text.
For teams on capacity pricing (slot reservations), replace the byte-billed dollar estimate with slot-hour consumption from total_slot_ms:
SELECT
user_email,
SUM(total_slot_ms) / 1000 / 3600 AS total_slot_hours
FROM
`region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE
creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
AND job_type = 'QUERY'
AND statement_type != 'SCRIPT'
GROUP BY
user_email
ORDER BY
total_slot_hours DESC;
If you are working through the full cost governance problem and want to understand how staging layer materialisation and repeated subqueries interact with your bill, our post on dbt staging layer materialisation in BigQuery covers the materialisation dimension, and repeated subqueries in dbt BigQuery addresses a common pattern that attribution work frequently surfaces as the root cause.
If you want to implement a structured cost attribution and query governance layer in your BigQuery environment, explore how Fintel Analytics approaches this — we work with growth-stage businesses globally to design and deliver exactly this kind of solution, from initial audit through to automated cost alerting.

What You Find When You Actually Run This
In our experience delivering this for clients, the results are almost always surprising — and always actionable. A few patterns that come up consistently:
The BI tool service account is your biggest spender. This is the most common finding. A Looker or Holistics BI service account running dashboard refreshes against unoptimised Explore definitions or unbounded date ranges can easily account for 30–50% of monthly compute. The query volume looks reasonable in isolation — the problem is each query scans far more than it should. Attribution surfaces this; the fix is usually a combination of partition filters, caching configuration, and materialised aggregations.
One dbt model is running full-table scans on every run. A single poorly written query scanning a 10 TiB table costs $62.50, and if that query runs every hour, it costs $1,500 per day. We have seen incremental models that — due to a missing is_incremental() partition filter condition — are running as effective full refreshes on every execution. The model appears correct in dbt's output (no errors, no test failures), but the bytes billed tell a different story. Attribution by model name makes this immediately visible.
Scheduled queries nobody remembers creating. A pattern we see repeatedly in companies that have been running for two or more years: scheduled native BigQuery queries — not managed by dbt — that were set up by someone who has since left, and are scanning millions of rows on an hourly schedule to produce a result that is no longer used downstream. The schema includes fields like creation_time, user_email, job_type, start_time, end_time, query, and total_bytes_billed — which makes it straightforward to identify these zombie jobs by correlating high-frequency, high-cost queries against service accounts with no downstream consumers.
Ad-hoc analyst queries in production datasets. Analysts with broad dataset access running exploratory SELECT * queries against raw or staging tables — sometimes from connected notebooks or direct console sessions — represent an ungoverned cost surface. Attribution by user_email and statement_type makes this quantifiable and gives you the evidence to enforce query governance without having it feel like a political argument.
Building a Cost Attribution Dashboard That Stays Current
Running the queries above manually once is useful for diagnosis. But cost attribution only becomes a governance tool when it is automated and visible to the right people.
The approach we recommend for growth-stage companies is straightforward:
Step 1 — Create a scheduled export. Build a dbt model (or a native BigQuery scheduled query) that materialises a cost attribution summary table daily. Pull the last 90 days of INFORMATION_SCHEMA.JOBS data, aggregate by user, model label, statement type, and day. Store the results in a dedicated analytics_ops or platform dataset.
Step 2 — Set dbt job labels. Add the following to your dbt_project.yml:
query-comment:
comment: "dbt model: {{ model.name }} | invocation: {{ invocation_id }}"
And configure job_labels in your BigQuery profile:
job_labels:
dbt_model: "{{ model.name }}"
dbt_invocation: "{{ invocation_id }}"
This populates the labels array on every BigQuery job dbt submits, enabling the label-based attribution query shown above.
Step 3 — Build a BI dashboard on the attribution table. A simple dashboard with total cost by day (trended), top 10 cost contributors by user, top 10 by dbt model, and a cost-per-run trend for your most expensive models gives your engineering lead everything they need to prioritise optimisation work. Update it on a daily schedule.
Step 4 — Set alerting thresholds. BigQuery budget alerts at the project level are a blunt instrument — they fire after the damage is done. A more useful pattern is a daily dbt test or Cloud Monitoring alert that fires if any single model's daily cost exceeds a defined threshold (e.g. more than $50 per day for a model that was costing $5 per day last week). This catches newly deployed models with cost regressions before they compound.
We worked with a Series A payments company that had watched its BigQuery bill triple over two quarters. Running a cost attribution analysis against INFORMATION_SCHEMA.JOBS identified three root causes: a Looker Explore without a default date filter scanning a 2-year raw events table on every dashboard load, a dbt incremental model running as a full refresh due to a missing partition filter condition, and a scheduled native query producing a report that had been superseded by a dashboard six months earlier. Addressing those three issues — none of which required significant refactoring — reduced monthly compute spend by over 60% within the next billing cycle.
Frequently Asked Questions
Q: What is BigQuery INFORMATION_SCHEMA cost attribution and why does it matter?
A: BigQuery INFORMATION_SCHEMA cost attribution is the practice of using the INFORMATION_SCHEMA.JOBS metadata view to identify which queries, users, dbt models, and scheduled jobs are responsible for your compute spend. It matters because BigQuery's billing dashboard shows you totals, not causes — and without attribution, cost optimisation is guesswork. For teams running dbt, BI tools, and scheduled queries simultaneously, attribution is the foundation of any meaningful cost governance.
Q: How do I find the most expensive queries in BigQuery?
A: Query INFORMATION_SCHEMA.JOBS_BY_PROJECT filtered by creation_time and job_type = 'QUERY', ordered by total_bytes_billed descending. Always filter on creation_time first to avoid scanning the full metadata history, and exclude statement_type = 'SCRIPT' to avoid double-counting jobs that contain child queries.
Q: Can I attribute BigQuery costs to specific dbt models?
A: Yes — but only if you have configured job_labels in your dbt BigQuery profile. Once labels are set, every query dbt submits carries the model name as a label, and you can aggregate total_bytes_billed from INFORMATION_SCHEMA.JOBS grouped by JSON_VALUE(labels, '$.dbt_model'). Without labels, you are left pattern-matching against raw query text, which works but breaks easily.
Q: Does querying INFORMATION_SCHEMA.JOBS itself cost money?
A: Yes — INFORMATION_SCHEMA queries consume bytes like any other BigQuery query. However, because the underlying data is partitioned by creation_time and clustered by project_id and user_email, a well-scoped attribution query (filtering on a 30-day window) is typically inexpensive. The risk is querying without a creation_time filter, which triggers a full scan of the metadata history.
Q: What is the difference between on-demand and capacity pricing for cost attribution purposes?
A: On on-demand pricing, your cost metric is total_bytes_billed — convert to TiB and multiply by your regional rate (currently $6.25 per TiB in the US multi-region as of 2026). On capacity (slot reservation) pricing, the relevant metric is total_slot_ms — divide by 1,000 to get slot-seconds, and by 3,600 to get slot-hours. Attributing slot-hours by user or model is just as important on capacity pricing, because inefficient queries consume slots that block other work even if the marginal cost looks lower.
If your BigQuery bill is growing faster than your business and you cannot point to a specific query or team as the cause, that is exactly the situation we help companies get out of. At Fintel Analytics, we have delivered cost attribution and query governance solutions for fintech, payments, and e-commerce businesses at Series A and beyond — identifying the three or four changes that move the bill, building the attribution dashboards that prevent the problem from returning, and doing it without disrupting the engineering work already in flight. If your team is ready to stop flying blind on cloud costs, the answer is closer than you think.
