Column-Level Lineage: What It Is and Why Incidents Need It
July 2026 · Dataobservability
Alerted #data-eng 0.8s ago.
Downstream impact · consumers at risk
Live console · pick a break, watch it get caught
Column-level lineage is a map of how each individual column in your warehouse is produced: which source columns feed it, through which transformations, and which downstream columns, models, and dashboards depend on it. It is more precise than table-level lineage, which only tells you that table A feeds table B. Column-level lineage tells you that orders.net_revenue is computed from orders.gross minus orders.discount, and that three dashboards read it. That precision is what turns an incident from a guessing game into a lookup: when a column breaks, you see exactly what it affects, and when a number looks wrong, you see exactly where it came from.
Last updated July 2026.
What is column-level lineage?
Column-level lineage traces data at the granularity of individual columns rather than whole tables. For any column, it answers two questions: what upstream columns and logic produced this value, and what downstream columns and reports consume it. It is built by parsing the SQL of every transformation in your warehouse and following each column through the joins, expressions, and aggregations that create it. The result is a directed graph where the nodes are columns, not tables, so you can trace a single field from a raw source all the way to the cell on a dashboard.
The distinction from table-level lineage matters more than it sounds. Table-level lineage tells you that raw.payments feeds staging.payments feeds marts.revenue. That is useful, but when marts.revenue.mrr is wrong, table-level lineage points at an entire upstream table with 40 columns and says "somewhere in here." Column-level lineage points at the two source columns and the one CASE expression that actually produce mrr, and ignores the other 38. On a real incident, that is the difference between reading one transformation and auditing a whole pipeline.
How is column-level lineage generated?
Column-level lineage is generated by static analysis of SQL. A lineage engine parses each query into an abstract syntax tree, resolves table and column references against the warehouse catalog, and then propagates each output column back to the input columns it depends on, following SELECT expressions, JOIN conditions, GROUP BY keys, window functions, and subqueries. Do that across every model in your project and you get a complete graph. It does not require running the data or sampling rows, because the dependencies are structural: they live in the SQL, not the values.
Two things make it hard in practice, which is why not every tool that claims column-level lineage does it well. First, dialect coverage: Snowflake, BigQuery, Redshift, and Databricks SQL each have their own functions and quirks, and a parser that handles one may choke on another. Second, dynamic SQL and SELECT *: a star expansion has to be resolved against the live schema, and templated or generated SQL has to be rendered before it can be parsed. dbt helps a lot here, because the compiled manifest gives a lineage engine fully-rendered SQL and a clean dependency graph to anchor to. If you already model in dbt, that compiled artifact is why column-level lineage is far more reliable on dbt projects than on hand-written SQL scattered across scripts.
Why incidents need column-level lineage
Most of the time and cost of a data incident is not the fix, it is the tracing. An alert fires that a metric looks wrong. Without lineage, an engineer opens the query, finds it reads three upstream tables, opens each of those, finds they read five more, and works backward by hand until they find the column that changed. That archaeology is the slow part, and it happens under pressure while stakeholders wait. Column-level lineage collapses it: the alert arrives already attached to the exact upstream column that changed and the exact downstream reports that consume it.
It works in both directions, and both matter. Downstream (impact analysis): a column is about to change or just broke, and you need to know every model and dashboard that depends on it before you ship or while you triage. Upstream (root-cause analysis): a number is wrong, and you need the shortest path back to the source column that caused it. Pairing lineage with monitoring is what makes an alert actionable rather than merely informative, which is why lineage and incident tracking belong together: the incident record should carry its own blast radius. Lineage also earns its keep well outside incidents, in audit and compliance work where you have to prove where a regulated field came from and who can see it, the same evidence trail that broader tracking of controls and obligations depends on.
Column-level lineage and dbt
If you use dbt, you already have table-level lineage in the form of the DAG that dbt builds from ref() and source(). What dbt does not give you natively is column-level lineage: the DAG knows model A depends on model B, but not that A.revenue depends specifically on B.amount. Several tools close that gap by parsing your compiled dbt SQL and building the column graph on top of the manifest, which is the most reliable place to compute lineage because the SQL is already rendered and the dependencies are explicit.
This is where an observability platform and lineage naturally combine. A tool that is dbt-native reads your manifest, computes column-level lineage across the whole warehouse (including tables outside dbt), and then attaches that lineage to its monitoring, so a freshness or anomaly alert on a column arrives with the downstream columns and dashboards it affects already listed. You get the map and the monitor in one place instead of maintaining a separate lineage tool and correlating by hand. Dataobservability builds column-level lineage across Snowflake, BigQuery, Databricks, and Redshift, connects read-only in about 15 minutes, and publishes its pricing from 99 dollars a month.
Frequently asked questions
What is column-level lineage?
Column-level lineage maps how each individual column is produced and consumed: which upstream source columns and transformations create it, and which downstream columns, models, and dashboards depend on it. It is more precise than table-level lineage, which only records that one table feeds another. Column-level lineage lets you trace a single field from a raw source to a dashboard cell, which is what makes incident triage a lookup instead of a hand search through pipelines.
What is the difference between column-level and table-level lineage?
Table-level lineage records dependencies between whole tables: table A feeds table B. Column-level lineage records dependencies between individual columns: A.revenue is computed from B.amount and B.discount. When a metric breaks, table-level lineage points at an entire upstream table and says the cause is somewhere inside it; column-level lineage points at the specific columns and expression that produce the metric, so you read one transformation instead of auditing a whole pipeline.
How is column-level lineage generated?
It is generated by static analysis of SQL. A lineage engine parses each query into a syntax tree, resolves table and column references against the warehouse catalog, and propagates each output column back to the input columns it depends on through selects, joins, group-bys, and window functions. It does not read table data, because the dependencies are structural and live in the SQL. dbt makes it more reliable by providing fully-compiled SQL and an explicit dependency graph.
Does dbt provide column-level lineage?
Not natively. dbt builds table-level lineage from ref() and source(), so its DAG knows model A depends on model B but not that a specific column in A comes from a specific column in B. Column-level lineage on dbt is provided by tools that parse your compiled dbt SQL and build the column graph on top of the manifest, which is the most reliable place to compute it because the SQL is already rendered and the dependencies are explicit.
Catch broken data before your stakeholders do
Connect your warehouse and get all five pillars monitoring in 15 minutes. Transparent pricing, no credit card.