Datatrail
Blog / Guides 9 min read

Snowflake Column-Level Lineage: How to Check a Column with GET_LINEAGE

Last updated August 2026 · Datatrail

Lineage map
Lineage mapped from query history. Read-only connection.
0

Read-only connection. Datatrail never moves or mutates your data.

There are three ways to check column-level lineage in Snowflake. The fastest is the SNOWFLAKE.CORE.GET_LINEAGE table function, which returns upstream or downstream lineage for a single column as rows you can query. The second is the Snowsight Lineage tab, where you hover a column in the side panel and select View Lineage. The third, and the only one that works in bulk, is querying SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY and unpacking the OBJECTS_MODIFIED column, which records how source columns map to target columns for every write. All three need Enterprise Edition or higher.

The catch that costs people an afternoon is that lineage in Snowflake only knows about data movement since November 2024, and it is retained for one year. If you are looking for the query that built a table three years ago, none of these will find it, and that is a limit of the feature rather than a mistake in your SQL.

How to check lineage of a column in Snowflake

Use GET_LINEAGE. It is a table function in the SNOWFLAKE.CORE schema, and for a single column it is the most direct answer available:

-- object_name, object_domain, direction, distance
SELECT *
FROM TABLE(SNOWFLAKE.CORE.GET_LINEAGE(
  'analytics.public.fct_orders.order_total_usd',
  'COLUMN',
  'UPSTREAM',
  3
));

The arguments are positional. object_name must be fully qualified, and for a column that means database, schema, table, then column. object_domain accepts COLUMN, TABLE, SEMANTIC_VIEW, STAGE, DATASET and MODULE. direction is UPSTREAM or DOWNSTREAM. The optional distance takes an integer from 1 to 5 and defaults to 5.

Each row you get back is one edge in the lineage graph, with the source and target object details, a distance value, and a status that tells you whether the column is active or masked. Because it is a table function, you can filter and join it like anything else, which is what makes it more useful than the UI for anything repeatable.

Two limits are worth knowing before you trust the output. Distance is capped at 5, so a column at the end of a seven-hop chain of views will not trace all the way back in one call. And the function returns at most 10 million rows, truncating silently if the graph is larger. Silent is the problem: you get a result that looks complete.

How to get the downstream lineage of a table in Snowflake

Same function, flip the direction and change the domain. This is the question most people are actually asking, because it is the one you need before you change or drop something:

SELECT *
FROM TABLE(SNOWFLAKE.CORE.GET_LINEAGE(
  'analytics.public.stg_orders',
  'TABLE',
  'DOWNSTREAM',
  5
));

To find every object a specific column feeds, which is the "column used by objects" question, keep the domain as COLUMN and point downstream:

SELECT
  target_object_name,
  target_column_name,
  target_object_domain,
  distance
FROM TABLE(SNOWFLAKE.CORE.GET_LINEAGE(
  'raw.stripe.charges.amount',
  'COLUMN',
  'DOWNSTREAM',
  5
))
ORDER BY distance;

That result set is the blast radius of a change to that one field, at least as far as Snowflake can see it. The gap is that Snowflake's lineage stops at the edge of the warehouse. A column feeding a Looker explore, a Power BI dataset, or a reverse ETL sync to Salesforce shows no downstream rows at all, because nothing about those consumers is recorded in the warehouse. That is the single biggest reason teams outgrow the native feature, and it is worth checking before you conclude a column is safe to drop.

Viewing column lineage in Snowsight

For a one-off look, the UI is faster than writing SQL. In Snowsight, open the object you care about and select the Lineage tab. In the side panel, hover over the column name and select View Lineage, then choose Upstream Lineage or Downstream Lineage. The Distance column tells you how far removed each result is in the chain.

You need VIEW LINEAGE on the account, plus the usual object privileges: SELECT and REFERENCES on the objects, and USAGE on the database and schema. If the Lineage tab is missing entirely, that account-level privilege is the first thing to check, ahead of anything else.

Getting column lineage in bulk from ACCESS_HISTORY

Neither of the methods above scales past a handful of columns. When you want lineage for an entire schema, or you want to keep a copy in your own tables, go to ACCESS_HISTORY directly.

The relevant field is OBJECTS_MODIFIED, which Snowflake populates for write operations. Inside it, each modified column carries a columnName and columnId, plus two source arrays that people routinely confuse:

  • directSources are the columns referenced directly in the write, for example the columns named in the SELECT of a CTAS.
  • baseSources are the original columns in the underlying base tables, traced through the whole chain.

If you are building a lineage graph, directSources gives you the edge-by-edge structure and baseSources gives you the shortcut from a derived column straight back to its origin. Pick based on which question you are answering, because they will not agree and neither is wrong.

SELECT
  om.value:objectName::string          AS target_table,
  cols.value:columnName::string        AS target_column,
  src.value:objectName::string         AS source_table,
  src.value:columnName::string         AS source_column,
  ah.query_start_time
FROM snowflake.account_usage.access_history ah,
     LATERAL FLATTEN(input => ah.objects_modified)              om,
     LATERAL FLATTEN(input => om.value:columns)                 cols,
     LATERAL FLATTEN(input => cols.value:directSources)         src
WHERE ah.query_start_time >= DATEADD(day, -30, CURRENT_TIMESTAMP())
  AND om.value:objectName::string = 'ANALYTICS.PUBLIC.FCT_ORDERS'
ORDER BY ah.query_start_time DESC;

Three practical notes. ACCESS_HISTORY lives in SNOWFLAKE.ACCOUNT_USAGE and carries no additional cost beyond the compute to query it, though the equivalent view in ORGANIZATION_USAGE is a premium view that does bill for compute and storage. Like every ACCOUNT_USAGE view it lags real time, so a query you just ran will not be there immediately. And this is a query log, not a dependency graph: it tells you what actually moved, which means a table nobody has written to in the window simply does not appear.

Which method should you use?

Method Best for Column-level Works in bulk Edition
GET_LINEAGE Tracing one column or table, up or down Yes Awkward, one object per call Enterprise
Snowsight Lineage tab A quick visual answer, sharing with a colleague Yes No Enterprise
ACCESS_HISTORY Building your own graph, auditing an entire schema Yes, via OBJECTS_MODIFIED Yes Enterprise
OBJECT_DEPENDENCIES Declared references between views and tables No, object-level only Yes All

One row deserves a footnote. OBJECT_DEPENDENCIES is the only option on Standard Edition, and it is genuinely useful for answering which views reference a table, but it records declared dependencies rather than observed data movement and it has no column granularity at all. If you are on Standard and you need column lineage, no amount of SQL will produce it from Snowflake's own metadata. Our guide to Snowflake data lineage goes deeper on what you can assemble from OBJECT_DEPENDENCIES and QUERY_HISTORY.

Why is Snowflake not showing lineage for my table?

Usually one of six documented reasons rather than a bug. Snowflake's lineage has real boundaries and they are not obvious from the UI.

  • The data moved before November 2024. Lineage tracking started then. Object dependencies from before that date are available, but data movement lineage is not.
  • It is older than a year. Both column lineage and object lineage are retained for one year.
  • The object is out of scope. Lineage is unavailable for shared databases, the SNOWFLAKE database and INFORMATION_SCHEMA. Temporary and dropped tables do not appear either.
  • It is a dynamic table. Dynamic tables show up in other objects' lineage graphs, but the Lineage tab does not appear for a dynamic table itself.
  • The queries were disjointed. Snowflake cannot track data movement that results from separate, unconnected queries. Unload to a stage in one query and load from it in another and the chain breaks, because nothing links the two.
  • It is a semantic view. Column lineage does not support semantic views.

The disjointed-query limit is the one that surprises people most, and it is worth understanding rather than working around. Lineage is inferred from what a single query read and wrote. Any pattern that splits a data movement across two statements, or routes it through an external process, produces a genuine gap that Snowflake has no way to close.

Does Snowflake have column-level lineage?

Yes. Column-level lineage reached general availability in Snowflake release 9.3 in February 2025, and it requires Enterprise Edition or higher. It covers data movement inside Snowflake from November 2024 onward, with one year of retention, and it is exposed through the Snowsight Lineage tab, the GET_LINEAGE function, and the OBJECTS_MODIFIED field in ACCESS_HISTORY. It is a real feature, not a marketing bullet, and for tracing a single column inside the warehouse it is often all you need.

Where it stops is coverage outside the warehouse and anything resembling continuous monitoring. It will not tell you which dbt models or dashboards depend on a column, it will not notify you when a schema changes, and it will not tell you that a column's null rate tripled overnight. Those are the questions that turn a lineage lookup into an actual answer, and they need something reading the warehouse alongside your transformation and BI layers.

What to do with the answer

Most people arrive at a column lineage query with one of three jobs in mind, and it is worth naming which one you have, because it changes what "done" looks like.

If you are about to change or drop a column, you want downstream lineage and you want it to be complete, which means the warehouse graph plus whatever reads it from outside. This is impact analysis, and a partial answer is worse than no answer, because it gives you confidence to ship the change.

If you are debugging a number that looks wrong, you want upstream lineage, and you want it fast, because you are in an incident. GET_LINEAGE with UPSTREAM and a distance of 5 is genuinely the right tool here.

If you are answering a compliance question, you need to know everywhere a particular kind of data ends up, and warehouse lineage is only part of that picture. Personal data spreads well beyond the warehouse into support tools, CRMs and log stores, so the practical starting point is usually a system that can find where a person's data lives across every system you run, with column lineage answering the warehouse portion of it.

Getting column lineage that covers dbt and your BI tools

Snowflake's native lineage is good at what it does and bounded by the warehouse. If your real question is which dashboards break when this column changes, you need the graph stitched across your transformation layer and your BI layer too.

That is what Datatrail does. Connect Snowflake with a read-only role and it parses query history and your dbt manifest into a column-level lineage graph that spans sources, models, exposures and dashboards, with no Enterprise Edition requirement and no November 2024 floor. It also learns each table's normal freshness, volume, null rate and column distributions, so a schema change or an anomaly arrives as an alert that already names the columns and dashboards affected, rather than as a query you have to remember to run.

For a wider view of the category, see our guides to data lineage tools and metadata management tools, or the specifics of Snowflake lineage in Datatrail. Pricing is published and the warehouse connection is read-only.

See how your data flows, end to end

Connect your warehouse read-only and map lineage, freshness, and downstream impact before a change breaks a dashboard. Planned transparent pricing, no card to start.