The Invoice With No Names
A warehouse bill is an aggregate over queries you cannot individually see. `query_tag` (Snowflake) or `labels` (BigQuery) do not make anything cheaper — they make the aggregate legible, by writing a model name onto each query before it runs. Attribution is not an optimisation; it is the precondition for one.
Wavelength’s Snowflake warehouse bills $114.60 a month for the nightly `dbt build`. Somewhere in that number is one model responsible for 60% of it. Open the invoice below and try to find it.
Commit to a guess before the itemisation appears — which of these six models is the biggest line?
A warehouse bill is an aggregate over queries you cannot individually see. `query_tag` (Snowflake) or `labels` (BigQuery) do not make anything cheaper — they make the aggregate legible, by writing a model name onto each query before it runs. Attribution is not an optimisation; it is the precondition for one.
Without a tag, a warehouse bill and a dbt project are two documents that do not talk to each other. Snowflake bills the WAREHOUSE — size times time running — never the query, so QUERY_HISTORY has no model column at all until you put one there yourself. `query_tag` (a project- or model-level config; dbt runs `ALTER SESSION SET QUERY_TAG` before the model and resets after) is how a dbt run’s SQL gets a name attached in the warehouse’s own accounting. Skip it, and every cost conversation in this chapter — every widget past this one — is unavailable to you in production, because the raw bill genuinely does not name its own biggest contributor.
- query_tag (Snowflake)
- Project-level `+query_tag:` or `{{ config(query_tag='...') }}`. dbt’s own docs flag a caveat: a mid-materialization failure can leave a SUBSEQUENT query running under the wrong tag — which matters precisely for cost attribution.
- labels (BigQuery)
- The config key is `labels`, a dict — NOT `query_label`. `+labels:` in dbt_project.yml or `{{ config(labels={...}) }}`. Values over 63 characters are silently truncated by BigQuery.
- Databricks: automatic since 1.11
- dbt-databricks 1.11+ auto-tags every query with `@@dbt_model_name`, `@@dbt_materialized`, `@@dbt_core_version` and `@@dbt_databricks_version`, queryable in `system.query.history` — zero config. Recently landed modern practice (Databricks blog, published 2026-07-01), not folklore.
- run_results.json is not query_history
- `execution_time` (schema v6, dbt Core 1.12) is dbt’s WALL CLOCK per node — not warehouse cost. A node that took 300s because it queued behind a saturated warehouse is indistinguishable in this file from one that burned 300s of compute. Separating them needs `query_history` or `INFORMATION_SCHEMA.JOBS`.
| Warehouse | Config key | Where it shows up | The catch |
|---|---|---|---|
| Snowflake | `query_tag` | QUERY_HISTORY.QUERY_TAG | A failed mid-run query can leave the NEXT one mistagged |
| BigQuery | `labels` (a dict, not `query_label`) | INFORMATION_SCHEMA.JOBS labels | Truncated silently past 63 characters |
| Databricks (1.11+) | none — automatic | `system.query.history` | Only as current as your dbt-databricks version |
None of these three make a query cheaper. All three make the bill legible enough to ask which model is expensive in the first place.
Assuming BigQuery’s attribution config is `query_label`, by analogy with Snowflake’s `query_tag`. The real key is `labels`, a dict. A model configured with a nonexistent key silently does nothing — no error, no warning — and every query still lands untagged in `INFORMATION_SCHEMA.JOBS`.
Shipping `query_tag` only after a cost review is already underway. Every query that ran before the tag existed is permanently untagged in `query_history`. You can price this month forward; last month’s invoice stays a document with no model names in it, forever.
Ship `query_tag` (or `labels`) in the same pull request as the model, not the pull request that adds cost monitoring — by then, months of history are already unattributed.
Read `query_history` before opening a cost calculator. A projected number is a guess; a tagged, queried number is a fact.
On Databricks 1.11+, verify the auto-tags actually appear in `system.query.history` once, rather than assuming the version bump alone did it.
None of the timings, transcripts or dollar figures below were captured from a live dbt Core 1.12.0 run against a real warehouse. Every second comes from this KB’s own published Wavelength run profile, run through a real discrete-event scheduler in the widgets — not a lookup table — so the reader’s own thread dial recomputes it the same way dbt’s scheduler would. Every dollar figure is computed from a cited, dated public rate card, with the rate itself editable in the calculators. Chapter 8, 10 and 11 carried the same note, for the same reason.
dbt Core 1.12.0, released 16 July 2026. Every transcript below uses this KB’s canonical production clock, 2026-07-25 06:00 UTC. Where the newer engine changes anything, it gets exactly one sentence, later in this chapter — Chapter 11 already handed its full story to Chapter 22.
The weak answer opens `run_results.json` and looks for the biggest `execution_time`. The strong answer starts one step earlier: without `query_tag` or `labels`, the warehouse bill has no model column at all, so the honest first move is checking whether attribution is even configured — and if it is not, saying so is the correct senior answer, not a workaround.
A warehouse bill has no model names until `query_tag` or `labels` puts them there — attribution is the precondition for every other question in this chapter, not an optional refinement.
