Which SQL statements are slow, wasteful or failing — and why. Heaviest statements, file pruning, disk spill, shuffle, cache cold-start, queuing, failures and workload mix.
system.query.historyWhat it is. A record of completed query/statement executions across the account's compute, with rich per-statement performance and I/O counters. This is the system-table equivalent of the SQL editor's Query History, exposed for SQL analytics rather than one-off UI inspection.
Grain. One row per completed statement execution (identified by statement_id). Aggregating queries in this folder roll that grain up to day × workspace × warehouse × statement_type × user, etc.; the detail queries (query_costly_statements, query_per_query_estimate_lane) keep the native one-row-per-statement grain.
Key columns these queries use:
statement_id — unique id of the statement execution (join/dedup key; row grain).statement_type — SELECT / INSERT / MERGE / UPDATE / etc.; used to split workload and to find write-heavy statements.statement_text — the SQL text. Used to detect the audit's own marker (ILIKE '%databricks_audit%') and, in detail queries, emitted only after emails and single-quoted string literals are stripped at source (shape kept, data values removed).execution_status — enum FINISHED / FAILED / CANCELED. Filters "successful only" lanes and drives the failed-queries report.error_message — failure text (de-valued at source). Note: empty under customer-managed keys (CMK).executed_by — the principal (user email or service-principal GUID). Always partial-masked in output (e.g. da**@**; GUIDs kept as opaque handles). identity_type is derived from whether it looks like an email.start_time — statement start timestamp; the sole date-window filter and the basis for day / hour_of_day / usage_hour bucketing.total_duration_ms — end-to-end wall time (includes waiting).execution_duration_ms — pure execution time; used as the cost proxy (warehouse DBUs are allocated ~ proportionally to it) for ranking and for the per-query estimate lane.total_task_duration_ms — summed task (CPU) time across the cluster; a parallelism/compute-intensity signal.waiting_for_compute_duration_ms — time waiting for compute to provision (cold-start / warehouse spin-up).waiting_at_capacity_duration_ms — time queued because the warehouse was at capacity.compilation_duration_ms — metadata/optimizer/compile time (planner-bound latency).read_bytes, read_files, read_partitions, read_rows — scan I/O; read_partitions is a post-pruning count, not "partitions pruned".pruned_files — files skipped by data/file pruning; pruning effectiveness = pruned_files / (pruned_files + read_files).produced_rows — rows returned to the client (distinct from read_rows).spilled_local_bytes — bytes spilled to local disk (memory pressure). Local spill only — there is no spilled_remote_bytes column in Databricks.shuffle_read_bytes — shuffle volume; a bad-join / shuffle-heavy signal.written_bytes, written_rows, written_files — write output; written_files vs written_rows flags the small-files / write-amplification problem.from_result_cache (boolean) — served from the result cache. Distinct from…read_io_cache_percent — % of scan bytes served from the disk/IO cache (NOT the same signal as from_result_cache).compute (struct) — compute.type (e.g. warehouse vs. serverless) and compute.warehouse_id. Serverless rows carry warehouse_id = NULL.query_source (nested struct) — provenance: query_source.job_info.job_id, .dashboard_id, .legacy_dashboard_id, .notebook_id, .alert_id, .genie_space_id, .sql_query_id. Each subfield is NULL when that entity wasn't involved, and multiple subfields can populate simultaneously (they are not execution-ordered), so single-winner CASE attribution is a heuristic.workspace_id — the workspace that ran the statement (every query groups by this).Availability.
SELECT on system.query.history (system schemas are access-controlled, granted per-metastore by an admin).query system schema may need to be enabled per-metastore and has been in Preview — an unqueryable/undisabled schema yields TABLE_OR_VIEW_NOT_FOUND.error_message comes back blank.system.billing.usage (referenced downstream only)Not read by any .sql file in this folder. Because system.query.history has no per-statement dollar/DBU column, the per-query estimate lane (query_per_query_estimate_lane) emits only the raw drivers (execution_duration_ms, total_task_duration_ms, read_bytes), and the actual dollar attribution happens downstream by weighting hourly warehouse DBUs from system.billing.usage and reconciling so per-query estimates sum to metered DBU. See the billing/cost domain README for that table's grain, columns, and availability.
system.* schemas aren’t uniformly available — several (e.g. system.query, system.lakeflow) are still in preview or must be enabled per-metastore. A query that returns nothing usually means its schema isn’t enabled for your account yet.system.billing.list_prices) unless a query explicitly joins account_prices.The top 1000 finished, non-cached SQL statements in the trailing window, ranked by execution time (a DBU-cost proxy), each carrying pruning, spill, shuffle, and scan counters plus a de-valued query text to diagnose the fix.
system.query.historyOne row = one heavy FINISHED statement. execution_duration_ms is the column that matters (Databricks has no per-query dollar column; warehouse DBUs are allocated in proportion to it, so ranking by it ~= ranking by cost within one warehouse). pct_of_warehouse_exec_ms shows how much of that warehouse's total execution time this single statement ate; statement_fingerprint groups repeat shapes.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledPreview not rolled outServerless / warehouse only
This is the single best 'what to tune first' worklist: Databricks exposes no per-query dollar column, so ranking by execution_duration_ms is the closest proxy for DBU spend, and the top of this list is where money is actually burning. The attached counters tell you the fix rather than just the symptom: poor pruning (full scans) points at partitioning/clustering, spilled_local_bytes points at an undersized warehouse or memory-heavy rewrite, and large shuffle_read_bytes flags a bad join. Fixing the handful of statements at the top typically moves the warehouse's bill more than any autoscaling knob.
WITH devalued AS (
SELECT
workspace_id,
statement_id,
statement_type,
-- executed_by: partial-mask (email -> da****@****, service-principal GUID as-is, else first-2 + ****).
CASE
WHEN executed_by IS NULL OR executed_by = '__REDACTED__' THEN executed_by
WHEN executed_by LIKE '%@%' THEN concat(substr(executed_by, 1, 2), '****@****')
WHEN executed_by RLIKE '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' THEN executed_by
ELSE concat(substr(executed_by, 1, 2), '****')
END AS executed_by,
compute.warehouse_id AS warehouse_id,
compute.type AS compute_type,
start_time,
execution_duration_ms,
waiting_for_compute_duration_ms,
total_task_duration_ms,
read_bytes,
read_files,
pruned_files,
read_partitions,
read_rows,
produced_rows,
spilled_local_bytes,
shuffle_read_bytes,
from_result_cache,
-- statement_text de-valued: strip emails, then replace every single-quoted literal with '?'
-- (chr(39) is the single quote). Keeps the query shape, removes literal data values.
regexp_replace(
regexp_replace(statement_text, '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+[.][A-Za-z]{2,}', '<email>'),
concat(chr(39), '[^', chr(39), ']*', chr(39)), '?'
) AS statement_text
FROM system.query.history
WHERE start_time >= dateadd(DAY, -:period_days, current_date())
AND start_time < current_date()
AND execution_status = 'FINISHED'
AND from_result_cache = false
AND execution_duration_ms > 0
),
base AS (
SELECT d.*, sha2(d.statement_text, 256) AS statement_fingerprint
FROM devalued d
)
SELECT
b.*,
-- per-warehouse framing: this statement's share of its warehouse's total execution time.
b.execution_duration_ms / NULLIF(SUM(b.execution_duration_ms) OVER (PARTITION BY b.warehouse_id), 0) AS pct_of_warehouse_exec_ms,
ROW_NUMBER() OVER (PARTITION BY b.warehouse_id ORDER BY b.execution_duration_ms DESC) AS exec_rank_in_warehouse,
-- status: worst-first band on within-warehouse execution share (field heuristic).
CASE
WHEN b.execution_duration_ms / NULLIF(SUM(b.execution_duration_ms) OVER (PARTITION BY b.warehouse_id), 0) >= :crit_wh_share THEN 'CRITICAL'
WHEN b.execution_duration_ms / NULLIF(SUM(b.execution_duration_ms) OVER (PARTITION BY b.warehouse_id), 0) >= :warn_wh_share THEN 'WARN'
ELSE 'OK'
END AS status
FROM base b
ORDER BY b.execution_duration_ms DESC
LIMIT :top_n:period_daysdefault 30rolling window in days:warn_wh_sharedefault 0.1fraction of a warehouse's total execution time one statement must eat to flag WARN:crit_wh_sharedefault 0.25same for CRITICAL:top_ndefault 1000row cap| Column | How to read it |
|---|---|
| workspace_id | The workspace that ran the statement; per-region scope, so attribute cost only within this workspace/region. |
| statement_id | Unique id of this statement execution; the row grain and the handle to look the query up in Query History. |
| statement_type | SELECT / INSERT / MERGE / UPDATE / etc.; tells you whether the heavy work is a read or a write. |
| executed_by | The principal who ran it, partial-masked (email as da****@****, service-principal GUID passed through as an opaque handle; NULL / '__REDACTED__' passed through unchanged); identifies the owner to talk to about a fix. |
| warehouse_id | The SQL warehouse that ran it (from compute.warehouse_id); NULL means serverless (which attributes cost differently). |
| compute_type | The compute.type (e.g. warehouse vs serverless); context for the warehouse_id NULLs. |
| start_time | When the statement started; the only date-window basis and useful for correlating with billing hours. |
| execution_duration_ms | Pure execution time in milliseconds; the cost proxy and the sort key, higher ~= more DBUs consumed. |
| waiting_for_compute_duration_ms | Milliseconds spent waiting for compute to provision (cold-start / warehouse spin-up), not execution. |
| total_task_duration_ms | Summed task (CPU) time across the cluster in ms; a parallelism / compute-intensity signal. |
| read_bytes | Bytes scanned from storage; raw scan volume. |
| read_files | Files actually read (post-pruning); denominator alongside pruned_files for pruning effectiveness. |
| pruned_files | Files skipped by data/file pruning; effectiveness = pruned_files / (pruned_files + read_files), low = full scans. |
| read_partitions | Partitions read after pruning (a post-pruning count, NOT partitions pruned). |
| read_rows | Rows scanned from source, distinct from rows returned. |
| produced_rows | Rows returned to the client; a huge read_rows with tiny produced_rows suggests scan amplification / weak filtering. |
| spilled_local_bytes | Bytes spilled to local disk (memory pressure); non-zero means the statement needs a bigger warehouse or a rewrite. Local spill only, no remote-spill column exists. Renamed disk_bytes_spilled downstream. |
| shuffle_read_bytes | Shuffle volume; large values flag a bad or exploding join. |
| from_result_cache | Whether it was served from the result cache; always false here (filtered out), shown for completeness. |
| statement_text | The de-valued SQL: emails replaced with <email> and single-quoted literals replaced with '?', so you see query shape/identifiers but never literal data values. |
no single statement dominates its warehouse - pct_of_warehouse_exec_ms below :warn_wh_share (field heuristic - tune for your account).
pct_of_warehouse_exec_ms at/above :warn_wh_share (WARN) or :crit_wh_share (CRITICAL) - one statement shape eating a large share of a warehouse's execution time (field heuristic).
| statement_id | statement_type | executed_by | warehouse_id | execution_duration_ms | read_files | pruned_files | spilled_local_bytes | shuffle_read_bytes |
|---|---|---|---|---|---|---|---|---|
| 01ef9a2b-3c4d-4e5f-8a9b-0c1d2e3f4a5b | SELECT | da****@**** | a1b2c3d4e5f60789 | 742000 | 18400 | 210 | 0 | 5368709120 |
| 01ef9a2b-7d8e-4f01-9a2b-3c4d5e6f7081 | MERGE | 01234567-89ab-cdef-0123-456789abcdef | a1b2c3d4e5f60789 | 518000 | 9600 | 95 | 4294967296 | 1073741824 |
| 01ef9a2b-aa11-4b22-8c33-4d55e66f7788 | SELECT | jo**** | NULL | 305000 | 540 | 23100 | 0 | 0 |
Start at the top of the list and triage each heavy statement by its signal: poor pruned_files/read_files ratio -> add or fix partitioning / liquid clustering / Z-order; non-zero spilled_local_bytes -> size up the warehouse or rewrite the memory-heavy step; large shuffle_read_bytes -> fix the join. Use statement_id to open the full plan in Query History and loop in the masked executed_by owner.
Feeds into: performance-tuning — the heaviest individual SQL statements by execution time (~= DBU cost),
A per-workspace, per-statement-type tally of the audit's own queries — how many ran, how long they took in wall-clock and task time, how many principals ran them, and the first/last time they landed — so you can see the cost footprint of running CrossHire itself.
system.query.historyOne row = a workspace + statement type describing how many queries this audit itself issued and how long they ran. The columns that matter are query_count and total_duration_secs - how much of your own query-history footprint running this audit adds.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledPreview not rolled outServerless / warehouse only
Any audit that reads system tables consumes warehouse compute of its own, and admins reasonably ask "what does running this cost me?" This query answers with full transparency: it deliberately includes the audit's own queries (the exact inverse of self-exclusion) so you can weigh the audit's runtime footprint against the savings it surfaces. Because Databricks exposes no per-query DBU or dollar figure, the honest deliverable is magnitude — counts and seconds of runtime — not a dollar bill; a dollar value only appears if you multiply by a DBU rate downstream.
SELECT
workspace_id,
statement_type,
COUNT(*) AS query_count,
SUM(total_duration_ms) / 1000.0 AS total_duration_secs,
SUM(COALESCE(total_task_duration_ms, 0)) / 1000.0 AS total_task_secs,
COUNT(DISTINCT executed_by) AS distinct_principals,
MIN(start_time) AS first_query_time,
MAX(start_time) AS last_query_time
FROM system.query.history
WHERE start_time >= dateadd(day, -:period_days, current_date())
AND statement_text ILIKE '%databricks_audit%'
GROUP BY workspace_id, statement_type
ORDER BY workspace_id, statement_type:period_daysdefault 30rolling window in daysdatabricks_audit marker in the SQL text — the deliberate inverse of self-exclusion.workspace_id and statement_type so you see which workspaces and which kinds of statements the audit spends its runtime on.| Column | How to read it |
|---|---|
| workspace_id | The workspace that ran the audit's queries; results are broken out per workspace. |
| statement_type | The kind of statement (e.g. SELECT) — the audit library is SELECT-only, so this is how its runtime splits by operation type. |
| query_count | Number of the audit's own statement executions in this workspace/type bucket over the window. Cumulative across audit runs, not a single run. |
| total_duration_secs | Summed end-to-end wall-clock time (seconds) of those statements, including any waiting. This is the headline magnitude, not a dollar cost. |
| total_task_secs | Summed task (CPU) time across the cluster (seconds); a compute-intensity/parallelism signal. NULLs are treated as 0 before summing. |
| distinct_principals | How many distinct principals (users or service principals) executed the audit's queries here — usually the audit's own service identity. |
| first_query_time | Earliest start timestamp of a matching audit query in the window. |
| last_query_time | Latest start timestamp of a matching audit query — because there is no upper bound on start_time, today's in-flight run is included. |
Inventory / reference query — it returns state to read, not a pass/fail band.
| workspace_id | statement_type | query_count | total_duration_secs | total_task_secs | distinct_principals | first_query_time | last_query_time |
|---|---|---|---|---|---|---|---|
| 1234567890123456 | SELECT | 95 | 41.8 | 310.5 | 1 | 2026-06-22 06:03:11 | 2026-07-06 09:14:22 |
| 9876543210987654 | SELECT | 95 | 58.3 | 402.1 | 2 | 2026-06-22 06:04:55 | 2026-07-06 09:15:07 |
Compare the audit's total runtime seconds against the savings the rest of the library surfaces to confirm the audit pays for itself; if you want a dollar figure, multiply the duration by your warehouse's DBU rate downstream. If query_count or total_duration_secs looks high, schedule the audit less frequently or point it at a smaller warehouse.
Feeds into: Audit Cost tab — what running THIS audit cost the workspace
A daily per-warehouse rollup that separates the two distinct cache signals (result-cache hits and disk/IO cache coverage) from cold-start and compile-bound latency.
system.query.historyOne row = a day + warehouse whose queries were checked for cache reuse. The columns that matter are read_io_cache_percent_avg (average share of scanned bytes served from disk/IO cache) and waiting_for_compute_ms_sum (cumulative cold-start/provisioning wait). A day with a low average and a high low_io_cache_count is spending compute re-reading data it should be caching.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledPreview not rolled outServerless / warehouse only
Cold-start and compile latency are pure waste: users wait while a warehouse spins up or a plan compiles, and you may be paying for that idle-to-warm time. This query tells you whether slow days are caused by warehouses stopping too aggressively (high compute-wait), planner-bound overhead (high compilation), or poor data-cache locality (low `read_io_cache_percent`) — each of which has a different fix, from auto-stop/warm-pool tuning to layout changes. It also quantifies how much your result cache is actually saving you, since every result-cache hit is near-free compute you didn't re-run.
SELECT date(start_time) AS day, workspace_id, compute.type AS compute_type, compute.warehouse_id AS warehouse_id,
COUNT(*) AS query_count,
SUM(CASE WHEN from_result_cache THEN 1 ELSE 0 END) AS result_cache_hit_count,
AVG(read_io_cache_percent) AS read_io_cache_percent_avg,
SUM(CASE WHEN read_io_cache_percent < :warn_io_cache_pct THEN 1 ELSE 0 END) AS low_io_cache_count,
SUM(waiting_for_compute_duration_ms) AS waiting_for_compute_ms_sum,
SUM(compilation_duration_ms) AS compilation_duration_ms_sum,
-- status: worst-first band on average IO-cache hit rate (field heuristic; :warn_io_cache_pct / :crit_io_cache_pct).
CASE
WHEN AVG(read_io_cache_percent) IS NULL THEN 'NOT_ASSESSED'
WHEN AVG(read_io_cache_percent) < :crit_io_cache_pct THEN 'CRITICAL'
WHEN AVG(read_io_cache_percent) < :warn_io_cache_pct THEN 'WARN'
ELSE 'OK'
END AS status
FROM system.query.history
WHERE start_time >= current_date() - INTERVAL :period_days DAYS
AND start_time < current_date()
GROUP BY date(start_time), workspace_id, compute.type, compute.warehouse_id
ORDER BY read_io_cache_percent_avg ASC:period_daysdefault 30rolling window in days:warn_io_cache_pctdefault 50average read_io_cache_percent below which a day+warehouse flags WARN, and the per-query low-cache threshold:crit_io_cache_pctdefault 20average read_io_cache_percent below which it flags CRITICALquery_count.| Column | How to read it |
|---|---|
| day | The calendar day (from statement start_time) the row summarizes; window is the last 30 complete days, excluding today. |
| workspace_id | The workspace that ran the statements. Every metric is scoped within this workspace. |
| compute_type | The compute kind from compute.type (e.g. warehouse vs. serverless). Distinguishes classic SQL warehouse rows from serverless rows. |
| warehouse_id | The SQL warehouse that ran the statements (compute.warehouse_id). NULL for serverless rows (serverless carries no warehouse_id) — so a NULL here is expected, not missing data. |
| query_count | Total statements executed on that warehouse-day (COUNT(*)). The denominator for the two cache counts below. |
| result_cache_hit_count | How many of those statements were served entirely from the result cache (from_result_cache = true) — near-free re-runs. Compare to query_count for a hit rate. |
| read_io_cache_percent_avg | Average percent of scanned bytes served from the disk/IO cache across statements. This is a DIFFERENT signal from result_cache_hit_count; higher means better data-cache locality. Statements with a NULL value (e.g. non-scan) are excluded from this average. |
| low_io_cache_count | Count of statements whose disk/IO cache coverage was under 50% (read_io_cache_percent < 50) — i.e. mostly cold reads from cloud storage. Statements with NULL coverage are NOT counted. High values point at eviction, cold caches, or poor data layout. |
| waiting_for_compute_ms_sum | Total milliseconds statements spent waiting for compute to provision (cold-start / warehouse spin-up) on that day. High = auto-stop too aggressive or warm-pool worth considering. |
| compilation_duration_ms_sum | Total milliseconds spent in metadata/optimizer/compilation (planner-bound latency), summed over the day. High = compile-bound, not compute-bound. |
read_io_cache_percent_avg at/above :warn_io_cache_pct (field heuristic - tune :warn_io_cache_pct for your account).
read_io_cache_percent_avg below :warn_io_cache_pct (WARN) or below :crit_io_cache_pct (CRITICAL) - field heuristic.
| day | workspace_id | compute_type | warehouse_id | query_count | result_cache_hit_count | read_io_cache_percent_avg | low_io_cache_count | waiting_for_compute_ms_sum | compilation_duration_ms_sum |
|---|---|---|---|---|---|---|---|---|---|
| 2026-06-14 | 1234567890123456 | WAREHOUSE | a1b2c3d4e5f60718 | 820 | 146 | 78.4 | 63 | 412000 | 58900 |
| 2026-06-14 | 1234567890123456 | WAREHOUSE | 0918f6e5d4c3b2a1 | 240 | 12 | 31.7 | 191 | 1985000 | 22400 |
| 2026-06-14 | 1234567890123456 | SERVERLESS | NULL | 510 | 88 | 64.2 | 97 | 0 | 40300 |
Rank warehouse-days by waiting_for_compute_ms_sum and compilation_duration_ms_sum to find the dominant latency driver: high compute-wait means loosen auto-stop or add a warm pool; high compilation means attack planner overhead; a high low_io_cache_count with low read_io_cache_percent_avg means improve data-cache locality (sizing, layout, or clustering). Use result_cache_hit_count vs query_count to confirm how much the result cache is already saving.
Feeds into: cache/cold-start (read_io_cache_percent)
A daily rollup of FAILED and CANCELED statement executions — counts, wasted runtime, and a de-valued error-message sample — sliced by workspace, warehouse, compute type, statement type, and (masked) user.
system.query.historyOne row = a day + warehouse + status + statement_type of failed or canceled queries, broken out by who ran them. The columns that matter are query_count (how many failed or were canceled that day) and error_message_sample (a de-identified shape of the most recent error) - a repeated high count on the same warehouse/statement_type points to a systemic issue (bad credentials, a broken job, schema drift), not one-off flakiness.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledPreview not rolled outServerless / warehouse only
Failed and canceled statements still consume warehouse time before they die, so they are pure wasted compute plus a reliability signal. Concentrations of failures on one warehouse, user, or statement type point at a broken job, a permissions/quota problem, or a query that should be rewritten or killed sooner. The de-valued `error_message_sample` lets an admin triage the failure shape (e.g. out-of-memory, timeout, access denied) without touching literal data values. Left unwatched, a looping failing job can burn DBUs indefinitely while producing nothing.
SELECT date(start_time) AS day, workspace_id, compute.type AS compute_type, compute.warehouse_id AS warehouse_id,
execution_status, statement_type,
CASE
WHEN executed_by IS NULL OR executed_by = '__REDACTED__' THEN executed_by
WHEN executed_by LIKE '%@%' THEN concat(substr(executed_by, 1, 2), '****@****')
WHEN executed_by RLIKE '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' THEN executed_by
ELSE concat(substr(executed_by, 1, 2), '****')
END AS executed_by,
COUNT(*) AS query_count,
SUM(total_duration_ms) AS total_duration_ms_sum,
SUM(execution_duration_ms) AS execution_duration_ms_sum,
-- error text de-valued at source: strip emails, then single-quoted string literals (chr(39) is
-- the single quote) - keeps the error SHAPE, drops literal data values.
regexp_replace(
regexp_replace(MAX(error_message), '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+[.][A-Za-z]{2,}', '<email>'),
concat(chr(39), '[^', chr(39), ']*', chr(39)), '?'
) AS error_message_sample,
-- status: worst-first band on daily failed/canceled query count (field heuristic; :warn_failed_count / :crit_failed_count).
CASE
WHEN COUNT(*) >= :crit_failed_count THEN 'CRITICAL'
WHEN COUNT(*) >= :warn_failed_count THEN 'WARN'
ELSE 'OK'
END AS status
FROM system.query.history
WHERE start_time >= current_date() - INTERVAL :period_days DAYS
AND start_time < current_date()
AND execution_status IN ('FAILED','CANCELED')
GROUP BY date(start_time), workspace_id, compute.type, compute.warehouse_id, execution_status, statement_type, executed_by
ORDER BY query_count DESC:period_daysdefault 30rolling window in days:warn_failed_countdefault 5failed/canceled query count per day+warehouse+statement_type that flags WARN:crit_failed_countdefault 20... that flags CRITICALtotal_duration_ms_sum, includes waiting) and pure execution time (execution_duration_ms_sum) for the failed work.| Column | How to read it |
|---|---|
| day | The calendar date (from statement start_time) the failures occurred on; one row per day per slice. Today is excluded. |
| workspace_id | The workspace that ran the failing statements. History is regional, so attribute within-region only. |
| compute_type | The compute kind from compute.type (e.g. warehouse vs. serverless). Classic/all-purpose clusters are never present here. |
| warehouse_id | The SQL warehouse that ran the work. NULL for serverless rows — a NULL is expected, not missing data. |
| execution_status | Either FAILED or CANCELED — the two non-success terminal states this report keeps. Separate rows per status. |
| statement_type | The statement kind (SELECT / INSERT / MERGE / etc.); tells you whether reads or writes are failing. |
| executed_by | The principal that ran it, partial-masked (emails as da****@****; service-principal GUIDs passed through as opaque handles; any other non-email principal masked as first-two-chars + ****; NULL/__REDACTED__ passed through). |
| query_count | Number of failed/canceled statements in that day-and-slice. An integer count, not cost. |
| total_duration_ms_sum | Summed end-to-end wall time in milliseconds across those statements, including time spent waiting/queued before failure. |
| execution_duration_ms_sum | Summed pure execution time in milliseconds — the wasted-compute cost proxy for the failures (no dollar figure exists). |
| error_message_sample | One representative error text (MAX) for the group, with emails replaced by <email> and single-quoted literals replaced by ? (shape kept, values removed). BLANK under customer-managed keys (CMK). |
query_count below :warn_failed_count per day+warehouse+statement_type (field heuristic - tune :warn_failed_count for your account).
query_count at/above :warn_failed_count (WARN) or :crit_failed_count (CRITICAL) - field heuristic; a spike concentrated on one warehouse or statement_type is the real signal.
| day | workspace_id | compute_type | warehouse_id | execution_status | statement_type | executed_by | query_count | total_duration_ms_sum | execution_duration_ms_sum | error_message_sample |
|---|---|---|---|---|---|---|---|---|---|---|
| 2026-07-04 | 1234567890123456 | WAREHOUSE | abc123def4567890 | FAILED | SELECT | da****@**** | 7 | 182450 | 141200 | [OUT_OF_MEMORY] Task failed while spilling; consider a larger warehouse |
| 2026-07-04 | 1234567890123456 | SERVERLESS | NULL | CANCELED | MERGE | c1f8a2b0-3d4e-4a1b-9c7d-2e5f6a7b8c9d | 3 | 905600 | 880100 | ? |
| 2026-07-03 | 9876543210987654 | WAREHOUSE | fe98dc76ba543210 | FAILED | INSERT | da****@**** | 2 | 41300 | 39800 | [INSUFFICIENT_PERMISSIONS] User <email> does not have permission on table ? |
Sort by `execution_duration_ms_sum` (or `query_count`) to find the warehouse, user, or job type burning the most compute on failures; read the `error_message_sample` to classify the cause (OOM -> bigger warehouse/rewrite, permission -> grant fix, timeout/cancel -> kill sooner or reschedule), then chase the offending job or user to stop repeated failing runs.
Feeds into: failed queries
A daily rollup of statements that spilled to local disk, showing per day, workspace, compute type, warehouse, and (masked) user how many queries spilled, how much they spilled, and their duration and shuffle context.
system.query.historyOne row = a day + warehouse + user whose queries spilled to local disk. The column that matters is spilled_local_bytes_sum - bytes a shuffle/sort/join had to write to local SSD because it did not fit in memory. Steady spill on the same warehouse means under-provisioned memory or a skewed query, not a one-off.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledPreview not rolled outServerless / warehouse only
Local disk spill means a statement ran out of memory and paged intermediate data to disk, which slows it dramatically and inflates the DBU bill because the warehouse stays busy longer. Because Databricks bills warehouse time (not per query), a handful of chronically spilling statements can quietly drive real overspend that no dollar column will show you. This report pinpoints exactly which day, warehouse, and user own the pressure so you can decide between sizing up the warehouse or rewriting the query — the two fixes that actually stop the bleed.
SELECT date(start_time) AS day, workspace_id, compute.type AS compute_type, compute.warehouse_id AS warehouse_id,
CASE
WHEN executed_by IS NULL OR executed_by = '__REDACTED__' THEN executed_by
WHEN executed_by LIKE '%@%' THEN concat(substr(executed_by, 1, 2), '****@****')
WHEN executed_by RLIKE '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' THEN executed_by
ELSE concat(substr(executed_by, 1, 2), '****')
END AS executed_by,
COUNT(*) AS spilling_query_count,
SUM(spilled_local_bytes) AS spilled_local_bytes_sum,
MAX(spilled_local_bytes) AS spilled_local_bytes_max,
SUM(total_duration_ms) AS total_duration_ms_sum,
SUM(execution_duration_ms) AS execution_duration_ms_sum,
SUM(shuffle_read_bytes) AS shuffle_read_bytes_sum,
-- status: worst-first band on daily local spill (field heuristic; :warn_spill_gb / :crit_spill_gb).
CASE
WHEN SUM(spilled_local_bytes) >= :crit_spill_gb * 1e9 THEN 'CRITICAL'
WHEN SUM(spilled_local_bytes) >= :warn_spill_gb * 1e9 THEN 'WARN'
ELSE 'OK'
END AS status
FROM system.query.history
WHERE start_time >= current_date() - INTERVAL :period_days DAYS
AND start_time < current_date()
AND spilled_local_bytes > 0
GROUP BY date(start_time), workspace_id, compute.type, compute.warehouse_id, executed_by
ORDER BY spilled_local_bytes_sum DESC:period_daysdefault 30rolling window in days:warn_spill_gbdefault 1daily local-spill GB per warehouse that flags WARN:crit_spill_gbdefault 10daily local-spill GB that flags CRITICAL| Column | How to read it |
|---|---|
| day | Calendar date the statements started (their bucket); the window is the last 30 complete days and excludes today. |
| workspace_id | The workspace that ran the statements. Every row is scoped to one workspace; do not attribute across workspaces or regions. |
| compute_type | The kind of compute (e.g. warehouse vs. serverless) from compute.type. Helps separate serverless from classic SQL-warehouse behavior. |
| warehouse_id | The SQL warehouse that ran the work. NULL for serverless rows — that is expected, not missing data. |
| executed_by | The principal that ran the statements, partial-masked (emails as da****@****, service-principal GUIDs passed through as opaque handles). NULL or __REDACTED__ passes through unchanged. |
| spilling_query_count | How many statements in this day/warehouse/user group spilled to local disk. Higher = more pervasive memory pressure, not just one outlier. |
| spilled_local_bytes_sum | Total bytes spilled to local disk across those statements, in bytes. The main severity measure. Note: local spill only — remote spill is not measurable in Databricks. |
| spilled_local_bytes_max | The largest single-statement spill in the group, in bytes. Use with the sum to tell a fat outlier from broad, steady spilling. |
| total_duration_ms_sum | Summed end-to-end wall time (including waiting) of the spilling statements, in milliseconds — the time cost you actually pay for. |
| execution_duration_ms_sum | Summed pure execution time of the spilling statements, in milliseconds; the cost proxy (DBUs track roughly proportional to it). |
| shuffle_read_bytes_sum | Total shuffle bytes read across the group, in bytes. High shuffle alongside spill often points at a bad join or oversized intermediate as the root cause. |
spilled_local_bytes_sum below :warn_spill_gb GB/day per warehouse (field heuristic - tune :warn_spill_gb for your account).
spilled_local_bytes_sum at/above :warn_spill_gb GB/day (WARN) or :crit_spill_gb GB/day (CRITICAL) - field heuristic; sustained daily spill on the same warehouse is the real signal.
| day | workspace_id | compute_type | warehouse_id | executed_by | spilling_query_count | spilled_local_bytes_sum | spilled_local_bytes_max |
|---|---|---|---|---|---|---|---|
| 2026-06-28 | 1234567890123456 | WAREHOUSE | abc123def4567890 | da****@**** | 12 | 48000000000 | 9000000000 |
| 2026-06-28 | 1234567890123456 | SERVERLESS | NULL | 3f9a2b1c-4d5e-6f70-8a90-b1c2d3e4f5a6 | 4 | 6500000000 | 3200000000 |
| 2026-06-27 | 9876543210987654 | WAREHOUSE | fed987cba6543210 | jo****@**** | 1 | 2100000000 | 2100000000 |
Sort by spilled_local_bytes_sum to find the worst day/warehouse/user hotspots, then for each: if spill is broad and steady (high spilling_query_count), size the warehouse up or move to a larger tier; if it's a single fat outlier (spilled_local_bytes_max ≈ sum) with high shuffle_read_bytes_sum, rewrite that statement's join/aggregation. Cross-check against the classic-compute coverage gap before concluding a warehouse is clean.
Feeds into: local spillage
One row per finished, non-cached warehouse statement, carrying the raw duration, task-time and scan-bytes drivers (bucketed by hour) that a downstream job weights to estimate each query's share of metered warehouse DBUs.
system.query.historyOne row = one finished, non-cached query on a SQL warehouse or serverless compute. The columns that matter are execution_duration_ms (used to allocate warehouse DBU cost across queries) and waiting_for_compute_duration_ms (should be netted out before allocating, since it is provisioning wait, not query work). There is no per-query dollar or DBU column here - use this as the weighting input for spreading system.billing.usage's hourly warehouse DBU total across the queries that ran in that hour, not as a cost figure on its own.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledPreview not rolled outServerless / warehouse only
Databricks exposes no per-query dollar or DBU column, so an admin cannot see which individual statements are actually driving a warehouse's bill. This query produces the load-bearing inputs that let a downstream process split each hour's metered warehouse DBUs across the statements that ran, turning an opaque hourly warehouse charge into an approximate cost-per-query. That per-statement estimate is what lets FinOps chargeback to teams, spot the handful of queries burning the most compute, and justify tuning or warehouse-sizing decisions with numbers rather than guesses.
SELECT date_trunc('HOUR', start_time) AS usage_hour, workspace_id,
compute.warehouse_id AS warehouse_id, compute.type AS compute_type,
CASE
WHEN executed_by IS NULL OR executed_by = '__REDACTED__' THEN executed_by
WHEN executed_by LIKE '%@%' THEN concat(substr(executed_by, 1, 2), '****@****')
WHEN executed_by RLIKE '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' THEN executed_by
ELSE concat(substr(executed_by, 1, 2), '****')
END AS executed_by,
statement_id, statement_type,
execution_duration_ms, waiting_for_compute_duration_ms, total_task_duration_ms,
read_bytes, total_duration_ms
FROM system.query.history
WHERE start_time >= current_date() - INTERVAL :period_days DAYS
AND start_time < current_date()
AND execution_status = 'FINISHED'
AND from_result_cache = false
AND execution_duration_ms > 0
AND compute.warehouse_id IS NOT NULL
ORDER BY usage_hour, warehouse_id, statement_id:period_daysdefault 30rolling window in daysusage_hour and tags it with workspace, warehouse, compute_type, masked user, and statement type so cost can be attributed within the right hour and warehouse.| Column | How to read it |
|---|---|
| usage_hour | The statement's start time truncated to the hour; the bucket used to line each query up against that hour's metered warehouse DBUs. |
| workspace_id | The workspace that ran the statement. Attribute only within-region and within-workspace when joining to billing. |
| warehouse_id | The SQL warehouse that executed the statement (from compute.warehouse_id). Always non-NULL here; serverless rows (NULL) are excluded by design. |
| compute_type | The compute kind from compute.type (e.g. WAREHOUSE). After serverless is excluded, this is the SQL-warehouse path. |
| executed_by | The principal, partial-masked: emails become da****@**** , service-principal GUIDs pass through as opaque handles, other non-email names are truncated to their first two chars plus ****, and __REDACTED__/NULL are left as-is. |
| statement_id | Unique id of this statement execution — the row grain and the join key downstream cost allocation reconciles against. |
| statement_type | The operation kind (SELECT / INSERT / MERGE / UPDATE / etc.). |
| execution_duration_ms | Pure execution time in milliseconds — the primary cost proxy; DBUs are allocated roughly in proportion to it (net of compute wait). |
| waiting_for_compute_duration_ms | Milliseconds spent waiting for compute to provision (cold-start / warehouse spin-up); subtracted from execution when weighting so idle wait is not billed as work. |
| total_task_duration_ms | Summed task (CPU) time across the cluster in milliseconds — an optional parallelism/compute-intensity weight. |
| read_bytes | Bytes scanned by the statement (bytes, not GB) — an optional I/O weight for the allocation. |
| total_duration_ms | End-to-end wall-clock time in milliseconds, including waiting; context, not the cost proxy itself. |
Inventory / reference query — it returns state to read, not a pass/fail band.
| usage_hour | warehouse_id | executed_by | statement_type | execution_duration_ms | waiting_for_compute_duration_ms | total_task_duration_ms | read_bytes | total_duration_ms |
|---|---|---|---|---|---|---|---|---|
| 2026-07-02T14:00:00Z | 0a1b2c3d4e5f6a7b | da****@**** | SELECT | 12000 | 3000 | 45000 | 5368709120 | 16000 |
| 2026-07-02T14:00:00Z | 0a1b2c3d4e5f6a7b | 9f8e7d6c-1a2b-3c4d-5e6f-7a8b9c0d1e2f | MERGE | 240000 | 8000 | 3600000 | 214748364800 | 255000 |
| 2026-07-02T09:00:00Z | 1122334455667788 | sv**** | INSERT | 800 | 0 | 1200 | 104857600 | 950 |
Feed this result into the downstream cost-allocation step: weight each hour's metered warehouse DBUs (from system.billing.usage) across these statements by execution_duration_ms (net of compute wait), optionally refined by total_task_duration_ms and read_bytes, and reconcile so per-query estimates sum to metered DBU. Then rank statements by estimated DBU to target the top spenders for tuning or warehouse right-sizing.
Feeds into: per-query ESTIMATE lane
A grouped rollup of the last 30 complete days of statement executions, attributing each to its originating source (job, dashboard, legacy_dashboard, notebook, alert, genie, sql_editor) and identity type (human user vs. service principal), with query counts, execution/wall-clock time, and scan bytes per workspace and warehouse.
system.query.historyOne row = a workspace + compute + identity + source_kind (job, dashboard, legacy_dashboard, notebook, alert, genie, sql_editor, or other) describing where queries came from. The columns that matter are source_kind (which surface issued the query) and query_count - a workspace dominated by "other" or split evenly between ad-hoc (sql_editor/notebook) and scheduled (job) sources tells you whether spend is coming from production pipelines or exploratory work.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledNo SELECT privilegePreview not rolled outServerless / warehouse only
This is the query that answers "is our compute spend scheduled automation or human ad-hoc exploration, and who drives it?" Ranking sources by execution time (the cost proxy) tells you whether to chase a runaway job, an over-eager dashboard refresh, or a handful of analysts running heavy interactive queries. Splitting user vs. service-principal identity separates governed pipeline spend from ungoverned ad-hoc usage, which is where surprise bills and untuned queries usually hide. Because attribution here is a documented heuristic, it is a triage lens for follow-up, not a billing-grade allocation.
-- NEEDS CONFIRMATION: nested-struct dotted-path access + CASE attribution precedence are UNVERIFIED.
-- Confirm the struct path resolves and decide whether to emit ALL non-null source flags
-- rather than a single CASE winner (simultaneous population is documented).
SELECT workspace_id,
compute.type AS compute_type, compute.warehouse_id AS warehouse_id,
CASE WHEN executed_by IS NULL THEN 'unknown'
WHEN executed_by LIKE '%@%' THEN 'user'
ELSE 'service_principal' END AS identity_type,
CASE WHEN executed_by IS NULL THEN executed_by
WHEN executed_by LIKE '%@%' THEN concat(substr(executed_by, 1, 2), '****@****')
ELSE executed_by END AS executed_by,
CASE WHEN query_source.job_info.job_id IS NOT NULL THEN 'job'
WHEN query_source.dashboard_id IS NOT NULL THEN 'dashboard'
WHEN query_source.legacy_dashboard_id IS NOT NULL THEN 'legacy_dashboard'
WHEN query_source.notebook_id IS NOT NULL THEN 'notebook'
WHEN query_source.alert_id IS NOT NULL THEN 'alert'
WHEN query_source.genie_space_id IS NOT NULL THEN 'genie'
WHEN query_source.sql_query_id IS NOT NULL THEN 'sql_editor'
ELSE 'other' END AS source_kind,
query_source.job_info.job_id AS job_id,
query_source.dashboard_id AS dashboard_id,
query_source.notebook_id AS notebook_id,
COUNT(*) AS query_count,
SUM(execution_duration_ms) AS execution_duration_ms_sum,
SUM(total_duration_ms) AS total_duration_ms_sum,
SUM(read_bytes) AS read_bytes_sum
FROM system.query.history
WHERE start_time >= current_date() - INTERVAL :period_days DAYS
AND start_time < current_date()
GROUP BY workspace_id, compute.type, compute.warehouse_id, source_kind,
CASE WHEN executed_by IS NULL THEN 'unknown'
WHEN executed_by LIKE '%@%' THEN 'user'
ELSE 'service_principal' END,
CASE WHEN executed_by IS NULL THEN executed_by
WHEN executed_by LIKE '%@%' THEN concat(substr(executed_by, 1, 2), '****@****')
ELSE executed_by END,
query_source.job_info.job_id, query_source.dashboard_id, query_source.notebook_id
ORDER BY workspace_id, compute_type, source_kind:period_daysdefault 30rolling window in daysjob, dashboard, legacy_dashboard, notebook, alert, genie, sql_editor, or other — so you can see scheduled vs. ad-hoc workload at a glance.user, a service_principal, or unknown and shows the partial-masked principal, revealing who drives each source.compute_type and warehouse_id), so serverless vs. classic-warehouse activity is separated.query_count) and summed execution time, total wall time, and scan bytes as the workload/cost-proxy magnitude.| Column | How to read it |
|---|---|
| workspace_id | The workspace that ran the statements. Provenance keys (job_id, dashboard_id, notebook_id) are only meaningful within this workspace — never join them across workspaces. |
| compute_type | From compute.type: the compute class (e.g. warehouse vs. serverless) the statements ran on. |
| warehouse_id | From compute.warehouse_id: the SQL warehouse. NULL for serverless rows, so a NULL here means serverless, not missing data. |
| identity_type | Derived from executed_by: `user` if it looks like an email (contains @), `service_principal` otherwise (e.g. a service-principal GUID), `unknown` if the principal is null. Separates governed automation from human ad-hoc runs. |
| executed_by | The principal, partial-masked: emails become `da****@****`; service-principal GUIDs pass through as opaque handles. Never a full email. |
| source_kind | The single attributed origin of the group — job / dashboard / legacy_dashboard / notebook / alert / genie / sql_editor / other. A HEURISTIC single-winner: multiple query_source subfields can be populated at once and are not execution-ordered, so treat this as a best-guess label. |
| job_id | The originating job id (from query_source.job_info.job_id) when the statement came from a job; NULL otherwise. Workspace-scoped. |
| dashboard_id | The originating dashboard id (from query_source.dashboard_id) when applicable; NULL otherwise. Workspace-scoped. |
| notebook_id | The originating notebook id (from query_source.notebook_id) when applicable; NULL otherwise. Workspace-scoped. |
| query_count | Number of statement executions in this group. An integer count, not a cost. |
| execution_duration_ms_sum | Summed pure execution time (milliseconds) across the group — the cost proxy; warehouse DBUs allocate roughly in proportion. Milliseconds, not dollars. |
| total_duration_ms_sum | Summed end-to-end wall time (milliseconds), including waiting/queuing. Compare against execution_duration_ms_sum to spot wait-heavy groups. |
| read_bytes_sum | Summed scan bytes read across the group. Bytes, not rows and not dollars; a scan-volume signal. |
Inventory / reference query — it returns state to read, not a pass/fail band.
| source_kind | identity_type | executed_by | warehouse_id | query_count | execution_duration_ms_sum | read_bytes_sum |
|---|---|---|---|---|---|---|
| job | service_principal | a1b2c3d4-5e6f-7890-abcd-ef1234567890 | abc123warehouse01 | 4820 | 932400000 | 150000000000 |
| sql_editor | user | da****@**** | abc123warehouse01 | 312 | 48720000 | 9800000000 |
| dashboard | user | mi****@**** | NULL | 975 | 61300000 | 22000000000 |
Sort groups by execution_duration_ms_sum to find the biggest workload sources, then drill in: if a service-principal `job` dominates, target that job_id for tuning or scheduling changes; if human `sql_editor` / `dashboard` activity is heavy, engage those users or add dashboard caching / warehouse right-sizing. Because attribution is heuristic, confirm the source before acting on any single row.
Feeds into: workload mix/hours; per-query ESTIMATE lane; per-WORKSPACE query provenance; editor-style ad-hoc (sql_editor / notebook) vs scheduled (job) split; who runs ad-hoc — top human users vs service principals (executed_by)
A daily rollup of files pruned versus files read (plus partitions, bytes, and rows scanned) for scan statements, sliced by workspace, warehouse, user, and statement type, so you can spot workloads doing full scans instead of pruning.
system.query.historyOne row = a day + warehouse + identity + statement_type whose queries pruned or read files during a scan. The columns that matter are pruned_files_sum and read_files_sum - together they give the pruning ratio (pruned / (pruned + read)); a low ratio on a repeating group means partition or file layout, or the query's own predicates, are not letting Databricks skip files it should be skipping.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledNo SELECT privilegePreview not rolled outServerless / warehouse only
File pruning is what lets a query read a small slice of a table instead of the whole thing; when pruning is weak, statements fall back to near-full scans that burn warehouse DBUs and wall-clock time for no benefit. This report tells you which warehouses, users, and statement types are reading far more files than they skip, which is the classic fingerprint of missing partitioning, absent clustering, or a table that would benefit from Z-order or liquid clustering. Because Databricks exposes no per-query dollar column, the scan volume here is your best proxy for wasted spend on inefficient reads. Fixing the worst offenders directly shrinks scan I/O and the compute time you pay for.
SELECT date(start_time) AS day, workspace_id, compute.type AS compute_type, compute.warehouse_id AS warehouse_id,
CASE
WHEN executed_by IS NULL OR executed_by = '__REDACTED__' THEN executed_by
WHEN executed_by LIKE '%@%' THEN concat(substr(executed_by, 1, 2), '****@****')
WHEN executed_by RLIKE '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' THEN executed_by
ELSE concat(substr(executed_by, 1, 2), '****')
END AS executed_by, statement_type,
COUNT(*) AS query_count,
SUM(pruned_files) AS pruned_files_sum,
SUM(read_files) AS read_files_sum,
SUM(read_partitions) AS read_partitions_sum,
SUM(read_bytes) AS read_bytes_sum,
SUM(read_rows) AS read_rows_sum,
-- status: worst-first band on the pruning ratio pruned/(pruned+read) (field heuristic; :warn_prune_ratio / :crit_prune_ratio).
CASE
WHEN SUM(pruned_files) + SUM(read_files) = 0 THEN 'NOT_ASSESSED'
WHEN SUM(pruned_files) / (SUM(pruned_files) + SUM(read_files)) < :crit_prune_ratio THEN 'CRITICAL'
WHEN SUM(pruned_files) / (SUM(pruned_files) + SUM(read_files)) < :warn_prune_ratio THEN 'WARN'
ELSE 'OK'
END AS status
FROM system.query.history
WHERE start_time >= current_date() - INTERVAL :period_days DAYS
AND start_time < current_date()
AND (pruned_files > 0 OR read_files > 0)
GROUP BY date(start_time), workspace_id, compute.type, compute.warehouse_id, executed_by, statement_type
ORDER BY (pruned_files_sum * 1.0 / NULLIF(pruned_files_sum + read_files_sum, 0)) ASC:period_daysdefault 30rolling window in days:warn_prune_ratiodefault 0.5pruning ratio (pruned_files / (pruned_files + read_files)) below which a group flags WARN:crit_prune_ratiodefault 0.2... below which it flags CRITICALpruned_files_sum) versus how many it actually read (read_files_sum) — the ratio pruned_files_sum / (pruned_files_sum + read_files_sum) is your pruning effectiveness.workspace_id, compute_type, warehouse_id, masked executed_by, and statement_type, so a low-pruning pattern points at a specific warehouse, user, and query shape.read_partitions_sum (partitions read after pruning), read_bytes_sum, and read_rows_sum — plus a query_count of how many statements rolled up.| Column | How to read it |
|---|---|
| day | Calendar date (from statement start_time) the activity was rolled up to. Covers the last 30 complete days; today is excluded. |
| workspace_id | The workspace that ran these statements. Every row is scoped to one workspace; do not sum across regions. |
| compute_type | Compute kind from compute.type (e.g. warehouse vs. serverless). Classic/all-purpose clusters never appear here at all. |
| warehouse_id | The SQL warehouse id (compute.warehouse_id). NULL for serverless rows — that is expected, not missing data. |
| executed_by | The principal that ran the statements. Emails are partial-masked (da****@****); service-principal GUIDs are passed through unchanged as opaque handles; NULL/__REDACTED__ pass through as-is. |
| statement_type | The statement kind (SELECT / INSERT / MERGE / etc.) for this slice; helps separate read queries from write-path scans. |
| query_count | Number of file-touching statements aggregated into this row (COUNT(*)). |
| pruned_files_sum | Total files skipped by data/file pruning across those statements. Higher is better; compare against read_files_sum to get the pruning ratio. |
| read_files_sum | Total files actually read. Large read_files_sum with small pruned_files_sum = near-full scans and a tuning target. |
| read_partitions_sum | Total partitions read AFTER pruning — a post-pruning count, NOT 'partitions pruned'. There is no total-partition denominator available. |
| read_bytes_sum | Total bytes scanned across the slice (bytes, not dollars). The raw I/O volume behind the file counts. |
| read_rows_sum | Total rows scanned across the slice (distinct from rows returned to the client). |
pruning ratio at/above :warn_prune_ratio (field heuristic - tune :warn_prune_ratio for your account).
pruning ratio below :warn_prune_ratio (WARN) or below :crit_prune_ratio (CRITICAL) - field heuristic; a stable low ratio on the same table/warehouse combination over several days is the real signal, not one bad day.
| day | workspace_id | compute_type | warehouse_id | executed_by | statement_type | query_count | pruned_files_sum | read_files_sum | read_bytes_sum |
|---|---|---|---|---|---|---|---|---|---|
| 2026-06-30 | 1234567890123456 | WAREHOUSE | abc123def4567890 | da****@**** | SELECT | 842 | 118 | 84200 | 48000000000 |
| 2026-06-30 | 1234567890123456 | SERVERLESS | NULL | f47ac10b-58cc-4372-a567-0e02b2c3d479 | MERGE | 57 | 51200 | 3900 | 210000000000 |
| 2026-06-29 | 1234567890123456 | WAREHOUSE | abc123def4567890 | da****@**** | SELECT | 1290 | 96400 | 1210 | 15000000000 |
Sort by read_files_sum descending and compute pruned_files_sum / (pruned_files_sum + read_files_sum) per row; for slices with a low ratio and high read_files_sum, identify the underlying tables and add or fix partitioning / clustering (Z-order or liquid clustering) so future scans skip more files.
Feeds into: pruning (pruned_files/read_files)
A daily, per-warehouse rollup that separates time queries spent queued because a warehouse was at capacity from time they spent waiting for compute to spin up.
system.query.historyOne row = a day + warehouse whose queries waited before or during execution. The columns that matter are waiting_at_capacity_ms_sum (queued because the warehouse was already at max concurrency/scale) and waiting_for_compute_ms_sum (compute was still provisioning - cold start). A warehouse that shows up here repeatedly on the same day-of-week/hour pattern is under-sized or under-scheduled for its load, not just unlucky once.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledServerless / warehouse only
Queuing waits are latency your users feel but your bill may not obviously explain, and the two kinds have opposite fixes. Time spent **queued at capacity** means the warehouse is under-provisioned for its concurrent load, so the fix is scaling out (more clusters / a bigger size). Time spent **waiting for compute** is cold-start latency, so the fix is keeping the warehouse warm (longer auto-stop, a warm pool) rather than adding capacity. Confusing one for the other wastes money by scaling up a warehouse whose real problem was that it kept shutting down, or by leaving a genuinely overloaded warehouse under-sized.
SELECT date(start_time) AS day, workspace_id, compute.type AS compute_type, compute.warehouse_id AS warehouse_id,
COUNT(*) AS query_count,
SUM(CASE WHEN waiting_at_capacity_duration_ms > 0 THEN 1 ELSE 0 END) AS queued_at_capacity_count,
SUM(CASE WHEN waiting_for_compute_duration_ms > 0 THEN 1 ELSE 0 END) AS waited_for_compute_count,
SUM(waiting_at_capacity_duration_ms) AS waiting_at_capacity_ms_sum,
SUM(waiting_for_compute_duration_ms) AS waiting_for_compute_ms_sum,
SUM(total_duration_ms) AS total_duration_ms_sum,
-- status: worst-first band on combined queue + cold-start seconds (field heuristic; :warn_queue_secs / :crit_queue_secs).
CASE
WHEN SUM(waiting_at_capacity_duration_ms) + SUM(waiting_for_compute_duration_ms) >= :crit_queue_secs * 1000 THEN 'CRITICAL'
WHEN SUM(waiting_at_capacity_duration_ms) + SUM(waiting_for_compute_duration_ms) >= :warn_queue_secs * 1000 THEN 'WARN'
ELSE 'OK'
END AS status
FROM system.query.history
WHERE start_time >= current_date() - INTERVAL :period_days DAYS
AND start_time < current_date()
GROUP BY date(start_time), workspace_id, compute.type, compute.warehouse_id
ORDER BY (waiting_at_capacity_ms_sum + waiting_for_compute_ms_sum) DESC:period_daysdefault 30rolling window in days:warn_queue_secsdefault 60combined queued-for-capacity + waiting-for-compute seconds per day+warehouse that flags WARN:crit_queue_secsdefault 600... that flags CRITICALqueued_at_capacity_count (warehouse full) versus waited_for_compute_count (compute provisioning / cold-start).| Column | How to read it |
|---|---|
| day | The calendar date (from statement start_time) the queries ran; the window is the last 30 complete days and excludes today. |
| workspace_id | The workspace that ran the statements; every row is scoped to one workspace. |
| compute_type | compute.type, the compute family the statements ran on (e.g. warehouse vs serverless). Distinguishes which family the waits came from. |
| warehouse_id | compute.warehouse_id, the specific SQL warehouse. NULL when the statement's compute has no attached warehouse id (typically non-warehouse / serverless compute) - treat a NULL as a real grouping attribute, not missing data. |
| query_count | Total statements executed in that warehouse-day; the denominator for the two wait counts. |
| queued_at_capacity_count | How many of those queries waited more than 0 ms because the warehouse was at capacity; a high share signals under-provisioning. |
| waited_for_compute_count | How many queries waited more than 0 ms for compute to provision (cold-start / spin-up); a high share signals the warehouse keeps stopping. |
| waiting_at_capacity_ms_sum | Total milliseconds spent queued at capacity across all queries that day; the aggregate scale-out pain. |
| waiting_for_compute_ms_sum | Total milliseconds spent waiting for compute to provision that day; the aggregate cold-start pain. |
| total_duration_ms_sum | Total end-to-end wall time (which includes waiting) summed across all queries; use as the baseline the wait sums are a fraction of. |
waiting_at_capacity_ms_sum + waiting_for_compute_ms_sum below :warn_queue_secs seconds/day per warehouse (field heuristic - tune :warn_queue_secs for your account).
combined wait at/above :warn_queue_secs seconds (WARN) or :crit_queue_secs seconds (CRITICAL) - field heuristic; waits that recur on the same warehouse are the real signal, not a single spike.
| day | workspace_id | compute_type | warehouse_id | query_count | queued_at_capacity_count | waited_for_compute_count | waiting_at_capacity_ms_sum | waiting_for_compute_ms_sum | total_duration_ms_sum |
|---|---|---|---|---|---|---|---|---|---|
| 2026-07-04 | 3419000000000000 | WAREHOUSE | a1b2c3d4e5f60718 | 1420 | 310 | 12 | 842000 | 9000 | 58230000 |
| 2026-07-04 | 3419000000000000 | SERVERLESS | NULL | 880 | 0 | 140 | 0 | 196000 | 21440000 |
| 2026-07-05 | 7720000000000000 | WAREHOUSE | f9e8d7c6b5a40312 | 95 | 2 | 44 | 1500 | 318000 | 4102000 |
For any warehouse-day where the wait sums are a meaningful slice of total_duration_ms_sum, act on which bucket dominates: if queued_at_capacity leads, scale the warehouse out (more clusters or a larger size); if waited_for_compute leads, keep it warm (lengthen auto-stop or add a warm pool) instead of adding capacity.
Feeds into: queuing (waiting_*)
A daily rollup of shuffle-read volume and write output (bytes, rows, files) for shuffle-heavy or write-heavy statements, sliced by workspace, compute type, warehouse, masked user, and statement type.
system.query.historyOne row = a day + warehouse + identity + statement_type whose queries shuffled data or wrote files. Two independent signals live in this row: shuffle_read_bytes_sum (a shuffle-heavy or skewed-join signal) and written_files_sum versus written_bytes_sum (write amplification - lots of small files instead of a few well-sized ones). Check both; a query can be shuffle-heavy without writing anything, or write badly without shuffling.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledPreview not rolled outServerless / warehouse only
Databricks exposes no per-query dollar column, so tuning targets have to be found through I/O signals. Heavy `shuffle_read_bytes_sum` usually means a bad or exploding join that is burning warehouse compute you are paying for; a high `written_files_sum` relative to `written_rows_sum` is the classic small-files-on-write pattern that slows every downstream read and forces later OPTIMIZE/compaction cycles. Pinpointing which day, warehouse, user, and statement type drive these lets an admin fix the query or clustering instead of paying repeatedly for the waste.
SELECT date(start_time) AS day, workspace_id, compute.type AS compute_type, compute.warehouse_id AS warehouse_id,
CASE
WHEN executed_by IS NULL OR executed_by = '__REDACTED__' THEN executed_by
WHEN executed_by LIKE '%@%' THEN concat(substr(executed_by, 1, 2), '****@****')
WHEN executed_by RLIKE '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' THEN executed_by
ELSE concat(substr(executed_by, 1, 2), '****')
END AS executed_by,
statement_type,
COUNT(*) AS query_count,
SUM(shuffle_read_bytes) AS shuffle_read_bytes_sum,
SUM(written_bytes) AS written_bytes_sum,
SUM(written_rows) AS written_rows_sum,
SUM(written_files) AS written_files_sum,
SUM(read_bytes) AS read_bytes_sum,
-- status: worst-first band on shuffle volume OR small-file write amplification (field heuristic;
-- :warn_shuffle_gb / :crit_shuffle_gb / :warn_avg_file_mb / :crit_avg_file_mb).
CASE
WHEN SUM(shuffle_read_bytes) >= :crit_shuffle_gb * 1e9
OR (SUM(written_files) > 0 AND SUM(written_bytes) / SUM(written_files) < :crit_avg_file_mb * 1e6) THEN 'CRITICAL'
WHEN SUM(shuffle_read_bytes) >= :warn_shuffle_gb * 1e9
OR (SUM(written_files) > 0 AND SUM(written_bytes) / SUM(written_files) < :warn_avg_file_mb * 1e6) THEN 'WARN'
ELSE 'OK'
END AS status
FROM system.query.history
WHERE start_time >= current_date() - INTERVAL :period_days DAYS
AND start_time < current_date()
AND (shuffle_read_bytes > 0 OR written_bytes > 0)
GROUP BY date(start_time), workspace_id, compute.type, compute.warehouse_id, executed_by, statement_type
ORDER BY shuffle_read_bytes_sum DESC:period_daysdefault 30rolling window in days:warn_shuffle_gbdefault 50daily shuffle_read_bytes per group that flags WARN:crit_shuffle_gbdefault 200... that flags CRITICAL:warn_avg_file_mbdefault 32average written-file size in MB below which a group flags WARN (small-file amplification):crit_avg_file_mbdefault 8... below which it flags CRITICALquery_count), shuffle bytes read, and bytes/rows/files written, plus bytes scanned.written_files_sum against written_rows_sum to spot small-files-on-write, and read shuffle_read_bytes_sum as a bad-join signal.| Column | How to read it |
|---|---|
| day | The calendar date (from statement start time) the activity is bucketed into; window is the last 30 complete days, excluding today. |
| workspace_id | The workspace that ran the statements; every metric is scoped to this workspace. |
| compute_type | The compute kind from compute.type (e.g. warehouse vs serverless). Classic/all-purpose clusters never appear here. |
| warehouse_id | The SQL warehouse that ran the statements. NULL for serverless rows, since serverless carries no warehouse_id. |
| executed_by | The principal that ran the statements, partial-masked (emails as da****@****, service-principal GUIDs passed through as opaque handles). |
| statement_type | The statement kind (SELECT / INSERT / MERGE / UPDATE / etc.); write-amplification concentrates in the write types. |
| query_count | Number of qualifying statements in this group (integer); each ran with nonzero shuffle or nonzero write. |
| shuffle_read_bytes_sum | Total shuffle bytes read across the group (bytes). High values flag bad-join / shuffle-heavy work. |
| written_bytes_sum | Total bytes written across the group (bytes). |
| written_rows_sum | Total rows written across the group (count). |
| written_files_sum | Total output files written (count). Large relative to written_rows_sum signals the small-files-on-write problem. |
| read_bytes_sum | Total bytes scanned across the group (bytes); scan context for the shuffle/write volume. |
shuffle_read_bytes_sum below :warn_shuffle_gb GB/day AND average written-file size at/above :warn_avg_file_mb MB (field heuristics - tune :warn_shuffle_gb / :warn_avg_file_mb for your account).
shuffle_read_bytes_sum at/above :warn_shuffle_gb GB (WARN) or :crit_shuffle_gb GB (CRITICAL), OR average written-file size below :warn_avg_file_mb MB (WARN) or :crit_avg_file_mb MB (CRITICAL) - field heuristics; either signal alone is enough to flag the row.
| day | workspace_id | compute_type | warehouse_id | executed_by | statement_type | query_count | shuffle_read_bytes_sum | written_files_sum | written_rows_sum |
|---|---|---|---|---|---|---|---|---|---|
| 2026-07-02 | 1234567890123456 | WAREHOUSE | abc123def4567890 | da****@**** | MERGE | 18 | 742000000000 | 9600 | 480000 |
| 2026-07-02 | 1234567890123456 | SERVERLESS | NULL | c7f1e2a3-4b5c-6d7e-8f90-1a2b3c4d5e6f | INSERT | 42 | 5300000000 | 51 | 12800000 |
| 2026-07-01 | 9988776655443322 | WAREHOUSE | ef98ab76cd5432ff | jo****@**** | SELECT | 7 | 1180000000000 | 0 | 0 |
Sort by `shuffle_read_bytes_sum` to find bad-join hotspots and by `written_files_sum` relative to `written_rows_sum` to find small-files writers, then drill into the offending warehouse/user/statement_type to rewrite the join or schedule an OPTIMIZE/compaction (and revisit partitioning or liquid clustering) on the target tables.
Feeds into: shuffle/write amplification
A day-by-hour histogram of query volume, runtime, scan bytes, and rows returned, sliced by workspace, compute type, warehouse, statement type, and (masked) user.
system.query.historyOne row = a day + hour-of-day + compute + statement_type + identity slice of your query workload. The columns that matter are query_count and total_duration_ms_sum - stack these by hour to see when your workload actually peaks, and whether that peak lines up with warehouse auto-scaling or scheduled auto-stop.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledNo SELECT privilegeServerless / warehouse only
Warehouse sizing, autoscaling, and auto-stop settings only pay off if they match when the work actually lands — this shows the peak-hour concentration and the off-hours tail that drive both idle spend and capacity queuing. Seeing which statement types and users dominate each hour tells you whether a spike is a nightly job wave you could reschedule or ad-hoc exploration you could warm-pool for. Because Databricks exposes no per-query dollar column, the summed execution time here is the practical proxy for where the compute burn concentrates. Getting the timing right is the difference between a warehouse that idles all night and one that queues every morning.
SELECT date(start_time) AS day, workspace_id, hour(start_time) AS hour_of_day,
compute.type AS compute_type, compute.warehouse_id AS warehouse_id,
statement_type,
CASE
WHEN executed_by IS NULL OR executed_by = '__REDACTED__' THEN executed_by
WHEN executed_by LIKE '%@%' THEN concat(substr(executed_by, 1, 2), '****@****')
WHEN executed_by RLIKE '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' THEN executed_by
ELSE concat(substr(executed_by, 1, 2), '****')
END AS executed_by,
COUNT(*) AS query_count,
SUM(CASE WHEN execution_status = 'FINISHED' THEN 1 ELSE 0 END) AS finished_count,
SUM(total_duration_ms) AS total_duration_ms_sum,
SUM(execution_duration_ms) AS execution_duration_ms_sum,
SUM(read_bytes) AS read_bytes_sum,
SUM(produced_rows) AS produced_rows_sum
FROM system.query.history
WHERE start_time >= current_date() - INTERVAL :period_days DAYS
AND start_time < current_date()
GROUP BY date(start_time), workspace_id, hour(start_time), compute.type, compute.warehouse_id, statement_type, executed_by
ORDER BY day, hour_of_day, workspace_id, compute_type, warehouse_id:period_daysdefault 30rolling window in daysstatement_type, and the (masked) user who ran it, so you can tell *what* and *who* drives each hour.read_bytes_sum) and rows-returned-to-client (produced_rows_sum) as intensity signals — note rows returned is not the same as rows scanned.| Column | How to read it |
|---|---|
| day | Calendar date (from statement start_time) of the bucket; ranges over the last 30 complete days. |
| workspace_id | Workspace that ran the statements. Every bucket is workspace-scoped; do not sum across regions. |
| hour_of_day | Hour 0-23 (of start_time) the work landed in; the axis for spotting peak vs. off-hours load. |
| compute_type | Compute class, e.g. warehouse vs. serverless. Distinguishes serverless rows (which have a NULL warehouse_id). |
| warehouse_id | SQL warehouse that ran the statements; NULL for serverless. Use to attribute an hour's load to a specific warehouse for sizing/auto-stop. |
| statement_type | Statement kind (SELECT / INSERT / MERGE / UPDATE / ...) — shows whether the hour is read-heavy or write-heavy. |
| executed_by | The principal who ran the work, partial-masked (emails -> da****@****; service-principal GUIDs kept whole; NULL/__REDACTED__ preserved). |
| query_count | Total statements in the bucket (all statuses). |
| finished_count | How many of query_count completed with status FINISHED; query_count minus this is failed/canceled. |
| total_duration_ms_sum | Summed end-to-end wall time (milliseconds) including waiting/queuing, across all statements in the bucket. |
| execution_duration_ms_sum | Summed pure execution time (milliseconds), excluding waits; the cost proxy for how much compute the bucket burned. |
| read_bytes_sum | Total bytes scanned in the bucket; a scan-intensity signal (bytes, not dollars). |
| produced_rows_sum | Total rows returned to the client in the bucket — distinct from rows scanned; large values flag heavy result materialization. |
Inventory / reference query — it returns state to read, not a pass/fail band.
| day | hour_of_day | compute_type | warehouse_id | statement_type | executed_by | query_count | finished_count | execution_duration_ms_sum | produced_rows_sum |
|---|---|---|---|---|---|---|---|---|---|
| 2026-06-30 | 8 | WAREHOUSE | a1b2c3d4e5f60718 | SELECT | da****@**** | 412 | 408 | 1875400 | 980245 |
| 2026-06-30 | 2 | SERVERLESS | NULL | MERGE | 9f3c2b1a-4d5e-6f70-8192-a3b4c5d6e7f8 | 37 | 37 | 642100 | 0 |
| 2026-06-30 | 8 | WAREHOUSE | a1b2c3d4e5f60718 | INSERT | jo****@**** | 15 | 13 | 88300 | 0 |
Overlay the day × hour_of_day histogram (query_count and execution_duration_ms_sum) to find each warehouse's peak hours, then align auto-stop/warm-pool windows and warehouse sizing to the concentration you see — and check whether off-hours load is scheduled jobs (inferred from statement_type and service-principal executors) worth rescheduling.
Feeds into: workload mix/hours
The heaviest SQL rolled up by statement shape (fingerprint), so a cheap statement run ten thousand times ranks next to one genuinely heavy query — and you can tell which one to fix.
system.query.historyOne row = one distinct statement SHAPE (statement_fingerprint) run one or more times. total_exec_ms (cumulative execution time across every run) is the column that matters - a cheap statement run 10,000 times can outweigh one heavy query. runs and avg_exec_ms tell you whether to fix the shape or its frequency.
RequiresSELECT on system.query; GA
Returns no rows ifSchema not enabledNo SELECT privilegeServerless / warehouse only
The per-statement ranking (query_costly_statements) shows individual runs, but a warehouse's real load is often one cheap shape run tens of thousands of times, not a single monster query. Grouping by statement_fingerprint collapses every run of the same de-valued shape into one row, so total_exec_ms — cumulative execution time — surfaces the true cost centre. It also tells you how to fix it: high runs with a low avg_exec_ms means fix the caller (frequency or caching); a high avg_exec_ms means tune the statement itself.
WITH devalued AS (
SELECT
statement_type,
compute.warehouse_id AS warehouse_id,
start_time,
execution_duration_ms,
regexp_replace(
regexp_replace(statement_text, '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+[.][A-Za-z]{2,}', '<email>'),
concat(chr(39), '[^', chr(39), ']*', chr(39)), '?'
) AS statement_text
FROM system.query.history
WHERE start_time >= dateadd(DAY, -:period_days, current_date())
AND start_time < current_date()
AND execution_status = 'FINISHED'
AND from_result_cache = false
AND execution_duration_ms > 0
),
fp AS (
SELECT sha2(statement_text, 256) AS statement_fingerprint, *
FROM devalued
)
SELECT
statement_fingerprint,
MAX(statement_type) AS statement_type,
min(statement_text) AS sample_statement_text,
COUNT(*) AS runs,
SUM(execution_duration_ms) AS total_exec_ms,
CAST(AVG(execution_duration_ms) AS BIGINT) AS avg_exec_ms,
MAX(execution_duration_ms) AS max_exec_ms,
COUNT(DISTINCT warehouse_id) AS distinct_warehouses,
MIN(start_time) AS first_seen,
MAX(start_time) AS last_seen,
-- status: worst-first band on cumulative execution time for the shape (field heuristic).
CASE
WHEN SUM(execution_duration_ms) >= :crit_total_hours * 3600 * 1000 THEN 'CRITICAL'
WHEN SUM(execution_duration_ms) >= :warn_total_hours * 3600 * 1000 THEN 'WARN'
ELSE 'OK'
END AS status
FROM fp
GROUP BY statement_fingerprint
ORDER BY total_exec_ms DESC
LIMIT :top_n:period_daysdefault 30rolling window in days:warn_total_hoursdefault 1cumulative execution hours for a single shape that flags WARN:crit_total_hoursdefault 5same for CRITICAL:top_ndefault 500row cap? and emails with <email> — then hashes it to a statement_fingerprint, so identical shapes that differ only in their values group together.FINISHED, non-cached statements up per fingerprint: runs, total_exec_ms, avg_exec_ms, max_exec_ms and how many warehouses ran the shape.OK / WARN / CRITICAL on cumulative execution hours against :warn_total_hours / :crit_total_hours — a labelled field heuristic, not a Databricks threshold — and returns worst-first, capped at :top_n.| Column | How to read it |
|---|---|
| statement_fingerprint | A stable hash of the de-valued statement shape. The same shape run with different literals shares one fingerprint — this is the grouping key. |
| runs | How many times the shape ran in the window. High runs with a low avg_exec_ms points at the caller — cache, dedupe or batch it rather than tuning the SQL. |
| total_exec_ms | Cumulative execution time across every run of the shape. This is the column that matters — the shape with the largest total is your biggest cost centre even when each run looks cheap. |
| avg_exec_ms | Mean execution time per run. A high average means the statement itself is heavy — tune the query or right-size its warehouse. |
| max_exec_ms | The slowest single run of the shape — a tail-latency signal separate from the average. |
| distinct_warehouses | How many warehouses ran the shape; a shape spread across many warehouses is a shared pattern worth fixing once. |
| status | OK / WARN / CRITICAL band on cumulative hours (:warn_total_hours / :crit_total_hours) — a labelled field heuristic you tune per account, never a vendor recommendation. |
total_exec_ms below :warn_total_hours hours over the window (field heuristic - tune for your account).
total_exec_ms at/above :warn_total_hours (WARN) or :crit_total_hours (CRITICAL) cumulative hours - a hot repeated shape (field heuristic). High runs + low avg_exec_ms means fix the caller (frequency / caching); high avg_exec_ms means tune the statement.
| statement_fingerprint | statement_type | runs | total_exec_ms | avg_exec_ms | max_exec_ms | distinct_warehouses | status |
|---|---|---|---|---|---|---|---|
| 3f9a…c2 | SELECT | 48,210 | 31,400,000 | 651 | 2,900 | 4 | CRITICAL |
| a17b…e9 | SELECT | 12 | 9,800,000 | 816,667 | 1,200,000 | 1 | WARN |
| 77c0… 41 | MERGE | 2,040 | 540,000 | 265 | 1,900 | 2 | OK |
FINISHED, non-result-cache statements with execution_duration_ms > 0 are counted — cache hits and failed runs are excluded by design, so the totals are execution time actually spent, not attempts.total_exec_ms is wall-clock execution time, not DBUs or dollars. system.query.history exposes no per-statement DBU column, so cost here stays a time magnitude — dollarise it only by pairing runtime with a warehouse's DBU rate. (Columns verified in a live workspace; the sibling of query_costly_statements.)Take the worst-first rows: where runs is high and avg_exec_ms is low, cache or dedupe the caller (free); where avg_exec_ms is high, tune the statement or right-size its warehouse before you scale it. The Where to go next links below jump to the individual runs behind a shape and to pruning effectiveness for shapes that over-scan.
Feeds into: Audit Performance tab — the query shapes that dominate warehouse execution time.