{"id":6748,"date":"2026-07-12T02:05:45","date_gmt":"2026-07-12T10:05:45","guid":{"rendered":"https:\/\/www.gudusoft.com\/data-lineage-examples\/"},"modified":"2026-07-12T02:05:45","modified_gmt":"2026-07-12T10:05:45","slug":"data-lineage-examples","status":"publish","type":"page","link":"https:\/\/www.gudusoft.com\/pt\/data-lineage-examples\/","title":{"rendered":"Data Lineage Examples: 6 Real Scenarios with SQL and Diagrams"},"content":{"rendered":"<p>UM <strong>exemplo de linhagem de dados<\/strong> shows, for a concrete piece of SQL, which source tables and columns feed which output columns, and through which transformations the data passes along the way. The six worked examples below cover the situations data engineers actually hit: a plain <code>INSERT-SELECT<\/code>, a multi-CTE transform, a chain of views, a stored procedure with dynamic SQL, a dbt model chain, and a cross-database ETL flow. Each example gives the SQL, then describes exactly what the lineage graph looks like for it.<\/p>\n\n\n\n<div class=\"wp-block-group alignfull has-background is-layout-constrained\" style=\"background-color:#eef7fb;padding-top:24px;padding-bottom:24px\"><div class=\"wp-block-group__inner-container\">\n\n<p><strong>Every example on this page is reproducible:<\/strong> paste the SQL into the <a href=\"https:\/\/sqlflow.gudusoft.com\/?utm_source=gudusoft&amp;utm_medium=website&amp;utm_campaign=data-lineage-examples\" target=\"_blank\" rel=\"noreferrer noopener\">free SQLFlow lineage visualizer<\/a> and you get the interactive diagram described below.<\/p>\n\n<\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">How to read these examples<\/h2>\n\n\n\n<p>Each lineage graph contains two kinds of edges. A <strong>direct<\/strong> edge means data physically flows from a source column into a target column, possibly through a function or aggregate. An <strong>indirect<\/strong> edge means a column shapes the result without landing in it: columns used in <code>WHERE<\/code>, <code>JOIN<\/code>, e <code>GROUP BY<\/code> clauses. Missing the indirect edges is how impact analysis goes wrong, because dropping a filter column breaks a report just as surely as dropping a selected one. If these terms are new, start with <a href=\"https:\/\/www.gudusoft.com\/pt\/o-que-e-linhagem-de-dados\/\">what data lineage is<\/a> e <a href=\"https:\/\/www.gudusoft.com\/pt\/column-level-data-lineage\/\">how column-level lineage works<\/a>, then come back.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Example 1: a simple INSERT-SELECT data lineage example<\/h2>\n\n\n\n<p>The starting point for any lineage discussion: one statement writing aggregated data into a summary table.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>INSERT INTO sales_summary (region, total_amount)\nSELECT c.region, SUM(o.amount)\nFROM orders o\nJOIN customers c ON o.customer_id = c.id\nWHERE o.status = 'completed'\nGROUP BY c.region;<\/code><\/pre>\n\n\n\n<p>The lineage graph has three tables (<code>orders<\/code>, <code>customers<\/code>, <code>sales_summary<\/code>) and these column-level edges:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Target column<\/th><th>Source column<\/th><th>Edge type<\/th><th>Via<\/th><\/tr><\/thead><tbody>\n<tr><td><code>sales_summary.region<\/code><\/td><td><code>customers.region<\/code><\/td><td>Direct<\/td><td>plain copy<\/td><\/tr>\n<tr><td><code>sales_summary.total_amount<\/code><\/td><td><code>orders.amount<\/code><\/td><td>Direct<\/td><td><code>SUM()<\/code><\/td><\/tr>\n<tr><td><code>sales_summary.*<\/code><\/td><td><code>orders.status<\/code><\/td><td>Indirect<\/td><td><code>WHERE<\/code> filter<\/td><\/tr>\n<tr><td><code>sales_summary.*<\/code><\/td><td><code>orders.customer_id<\/code>, <code>customers.id<\/code><\/td><td>Indirect<\/td><td><code>JOIN<\/code> condition<\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p>Note what the graph already tells you that a table-level view would not: <code>customers.email<\/code> and every other untouched column play no role here, and <code>orders.status<\/code> matters even though it never appears in the output. Many lineage tools skip that last edge entirely; SQLFlow models direct and indirect lineage as separate, toggleable relationship types.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Example 2: multi-CTE transformation<\/h2>\n\n\n\n<p>CTEs are where hand-drawn lineage diagrams start to lie, because each CTE is a temporary relation the graph must pass through without losing track of the original sources.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>WITH daily AS (\n  SELECT order_date, customer_id, SUM(amount) AS day_total\n  FROM orders\n  GROUP BY order_date, customer_id\n),\nranked AS (\n  SELECT customer_id, day_total,\n         ROW_NUMBER() OVER (PARTITION BY customer_id\n                            ORDER BY day_total DESC) AS rn\n  FROM daily\n)\nSELECT customer_id, day_total AS best_day_revenue\nFROM ranked\nWHERE rn = 1;<\/code><\/pre>\n\n\n\n<p>The lineage graph renders <code>daily<\/code> e <code>ranked<\/code> as intermediate nodes, so the full path of the interesting column reads: <code>orders.amount<\/code> &rarr; <code>SUM()<\/code> &rarr; <code>daily.day_total<\/code> &rarr; <code>ranked.day_total<\/code> &rarr; <code>best_day_revenue<\/code>. The window function creates a subtler chain: <code>rn<\/code> is computed from <code>customer_id<\/code> (partition) and <code>day_total<\/code> (ordering), and then <code>WHERE rn = 1<\/code> makes <code>rn<\/code> an indirect input to every output column. So <code>orders.amount<\/code> influences <code>best_day_revenue<\/code> twice: directly through the sum, and indirectly through the ranking that decides which row survives. A resolver that only pattern-matches column names cannot see that second path.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Example 3: a chain of views<\/h2>\n\n\n\n<p>Views stack. In a mature warehouse a report often sits three or four view definitions away from physical tables, and the question &#8220;where does this number really come from?&#8221; requires walking the whole chain.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE VIEW v_active_customers AS\nSELECT id, name, email\nFROM customers\nWHERE status = 'active';\n\nCREATE VIEW v_customer_revenue AS\nSELECT a.id, a.name, SUM(o.amount) AS revenue\nFROM v_active_customers a\nJOIN orders o ON o.customer_id = a.id\nGROUP BY a.id, a.name;<\/code><\/pre>\n\n\n\n<p>The graph shows <code>v_customer_revenue.revenue<\/code> tracing through the view boundary back to <code>orders.amount<\/code>, with <code>customers.status<\/code> attached as an indirect edge inherited from the first view&#8217;s filter. It also exposes something useful in the other direction: <code>v_active_customers.email<\/code> is selected by the first view but consumed by nothing downstream. Dead columns like this are exactly what you want to find before a cleanup or migration, and they are invisible at table level. When view definitions use <code>SELECT *<\/code>, SQLFlow expands the star against the schema so every inherited column still resolves to its physical source.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Example 4: stored procedure with dynamic SQL<\/h2>\n\n\n\n<p>This is the example that separates lineage tools, because the target table name does not even exist as literal text in the code. Here is a SQL Server procedure that builds a monthly snapshot through a temp table and a dynamically assembled <code>INSERT<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE PROCEDURE dbo.load_monthly_snapshot @month VARCHAR(7) AS\nBEGIN\n  SELECT o.customer_id, SUM(o.amount) AS month_total\n  INTO #monthly\n  FROM dbo.orders o\n  WHERE CONVERT(VARCHAR(7), o.order_date, 126) = @month\n  GROUP BY o.customer_id;\n\n  DECLARE @sql NVARCHAR(MAX) =\n      N'INSERT INTO dbo.snapshot_monthly (customer_id, month_total) '\n    + N'SELECT customer_id, month_total FROM #monthly';\n  EXEC sp_executesql @sql;\nEND<\/code><\/pre>\n\n\n\n<p>The correct lineage graph hops through both obstacles: <code>orders.amount<\/code> &rarr; <code>SUM()<\/code> &rarr; <code>#monthly.month_total<\/code> &rarr; <code>snapshot_monthly.month_total<\/code>, with <code>orders.order_date<\/code> as an indirect edge through the <code>WHERE<\/code> clause and the <code>@month<\/code> parameter tracked as an input to that filter. Getting there requires a real T-SQL procedural parser (not a statement splitter), temp-table tracking across statements, and resolution of the SQL string handed to <code>sp_executesql<\/code>. SQLFlow has dedicated procedural parsers for SQL Server T-SQL and Oracle PL\/SQL, resolves dynamic SQL inside procedures, and additionally renders a call graph of which procedures invoke which, so procedure-to-procedure lineage is visible too. Dynamic SQL is the most common blind spot in lineage tooling; if you evaluate any tool, test it with this pattern first.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Example 5: dbt model chain<\/h2>\n\n\n\n<p>dbt gives you model-level lineage out of the box via <code>ref()<\/code>, but the interesting question is column-level: which source field feeds which mart column, through which staging renames?<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-- models\/staging\/stg_orders.sql\nSELECT id AS order_id, customer_id, amount, status\nFROM {{ source('raw', 'orders') }}\n\n-- models\/marts\/fct_customer_revenue.sql\nSELECT customer_id, SUM(amount) AS lifetime_revenue\nFROM {{ ref('stg_orders') }}\nWHERE status = 'completed'\nGROUP BY customer_id<\/code><\/pre>\n\n\n\n<p>Import the project&#8217;s dbt manifest into SQLFlow and the graph resolves the templated references into a concrete column chain: <code>raw.orders.amount<\/code> &rarr; <code>stg_orders.amount<\/code> &rarr; <code>SUM()<\/code> &rarr; <code>fct_customer_revenue.lifetime_revenue<\/code>, with the rename <code>id<\/code> &rarr; <code>order_id<\/code> preserved in the staging hop and <code>status<\/code> carried as an indirect edge from the mart&#8217;s filter. Because the manifest ties models to the warehouse objects they build, the same graph reconciles dbt-managed lineage with everything else that touches those tables outside dbt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Example 6: cross-database ETL<\/h2>\n\n\n\n<p>Real estates span engines. A typical flow: an operational SQL Server database is extracted to cloud storage, loaded into Snowflake staging, then transformed into an analytics schema. The load-and-transform side looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-- Snowflake\nCOPY INTO staging.orders_raw\nFROM @s3_extract\/orders\/;\n\nINSERT INTO analytics.fct_orders (order_id, customer_id, amount_usd)\nSELECT r.order_id, r.customer_id, r.amount * fx.rate\nFROM staging.orders_raw r\nJOIN staging.fx_rates fx ON fx.currency = r.currency;<\/code><\/pre>\n\n\n\n<p>Within the Snowflake side, the graph shows <code>amount_usd<\/code> as a computed column with two direct parents, <code>orders_raw.amount<\/code> e <code>fx_rates.rate<\/code>, joined through the arithmetic expression, plus <code>currency<\/code> as an indirect edge from the join condition. To cover the whole pipeline, you feed SQLFlow both sides in their own dialects: the T-SQL extract logic parsed with the SQL Server parser and the Snowflake scripts parsed with the Snowflake parser, out of 39 dialect-specific parsers total. In enterprise deployments the results land in a persistent lineage repository that batch-scans estates of 100+ databases and over a million columns, so the end-to-end SQL Server-to-Snowflake picture lives in one graph and can be exported to DataHub, Microsoft Purview, or OpenMetadata.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What all six examples have in common<\/h2>\n\n\n\n<p>Every graph above was derived purely from SQL text and schema metadata. That is the core method behind <a href=\"https:\/\/www.gudusoft.com\/pt\/ferramenta-de-linhagem-de-dados-sql\/\">SQL-based data lineage<\/a>: since the transformation logic already lives in SQL, static analysis of that SQL reconstructs the lineage without agents, without query-log access, and without ever reading a row of your data. The differences between the examples are only in how hard the parsing gets, from a single <code>INSERT-SELECT<\/code> to dynamic SQL inside a procedure.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Example<\/th><th>What it exercises<\/th><th>Hard part<\/th><\/tr><\/thead><tbody>\n<tr><td>1. INSERT-SELECT<\/td><td>Direct vs indirect edges<\/td><td>Modeling <code>WHERE<\/code>\/<code>JOIN<\/code> columns as lineage<\/td><\/tr>\n<tr><td>2. Multi-CTE<\/td><td>Intermediate relations<\/td><td>Window function inputs; filter-on-rank path<\/td><\/tr>\n<tr><td>3. View chain<\/td><td>Nested definitions<\/td><td>Star expansion; finding dead columns<\/td><\/tr>\n<tr><td>4. Stored procedure<\/td><td>Procedural code<\/td><td>Temp tables and dynamic SQL resolution<\/td><\/tr>\n<tr><td>5. dbt chain<\/td><td>Templated SQL<\/td><td>Resolving <code>ref()<\/code>\/<code>source()<\/code> to real objects<\/td><\/tr>\n<tr><td>6. Cross-database ETL<\/td><td>Multiple dialects<\/td><td>Stitching per-engine graphs into one repository<\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Frequently asked questions<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">What is a data lineage diagram?<\/h3>\n\n\n<p>A data lineage diagram is a directed graph in which nodes are tables, views, or columns and edges show how data flows between them through SQL transformations. Column-level diagrams, like the ones described on this page, draw one edge per source-column-to-target-column relationship, annotated with the function or clause that created it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can I reproduce these examples myself?<\/h3>\n\n\n<p>Yes. Paste any of the SQL snippets above into the <a href=\"https:\/\/sqlflow.gudusoft.com\/?utm_source=gudusoft&amp;utm_medium=website&amp;utm_campaign=data-lineage-examples\">free SQLFlow visualizer<\/a>, pick the matching dialect, and the interactive diagram appears in seconds. The cloud edition has a free tier.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What is the difference between direct and indirect lineage?<\/h3>\n\n\n<p>Direct lineage means data flows from a source column into a target column, for example <code>SUM(orders.amount)<\/code> feeding <code>total_amount<\/code>. Indirect lineage means a column influences the result without appearing in it, such as a <code>WHERE<\/code> filter or <code>JOIN<\/code> key. SQLFlow tracks both as separate, toggleable edge types; most tools capture only direct flow.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can lineage be extracted from stored procedures and dynamic SQL?<\/h3>\n\n\n<p>Yes, but it needs a procedural parser, not just a query parser. SQLFlow has dedicated parsers for Oracle PL\/SQL and SQL Server T-SQL, traces lineage through parameters and temp tables, resolves SQL strings built inside procedures, and draws the procedure-to-procedure call graph.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do open-source tools handle these examples?<\/h3>\n\n\n<p>Partially. Libraries like <code>sqllineage<\/code> e <code>sqlglot<\/code> are genuinely good at parsing individual queries, and they handle examples 1 and 2 well. The examples worth testing before you commit are 4 through 6: procedural code with dynamic SQL, indirect lineage as a separate edge type, and stitching a multi-dialect estate into one interactive graph. Run your hardest stored procedure through any tool you evaluate and compare the output.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Which SQL dialects do these examples work with?<\/h3>\n\n\n<p>SQLFlow ships 39 dialect-specific parsers, including Snowflake, BigQuery, Redshift, Databricks, Oracle, SQL Server, PostgreSQL, MySQL, Teradata, Hive, Spark SQL, and Trino. The examples above use generic SQL, T-SQL, dbt-templated SQL, and Snowflake syntax, and each is parsed by its own grammar rather than a generic ANSI one.<\/p>\n\n\n\n<div class=\"wp-block-group alignfull has-background is-layout-constrained\" style=\"background-color:#60d5f6;padding-top:32px;padding-bottom:32px\"><div class=\"wp-block-group__inner-container\">\n\n<h2 class=\"wp-block-heading\">Turn your own SQL into a lineage diagram<\/h2>\n\n\n<p>Paste a query, a procedure, or a whole script into the free visualizer and get the column-level graph in seconds. For estates of many databases, talk to us about the enterprise repository.<\/p>\n\n\n<div class=\"wp-block-buttons is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/sqlflow.gudusoft.com\/?utm_source=gudusoft&amp;utm_medium=website&amp;utm_campaign=data-lineage-examples\" target=\"_blank\" rel=\"noreferrer noopener\">Try SQLFlow free<\/a><\/div>\n\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/www.gudusoft.com\/pt\/contato\/\">Request an enterprise demo<\/a><\/div>\n<\/div>\n\n<\/div><\/div>\n\n\n\n<script type=\"application\/ld+json\">{\n    \"@context\": \"https:\\\/\\\/schema.org\",\n    \"@graph\": [\n        {\n            \"@type\": \"TechArticle\",\n            \"headline\": \"Data Lineage Examples: 6 Real Scenarios with SQL and Diagrams\",\n            \"description\": \"Six worked data lineage examples with real SQL: INSERT-SELECT, multi-CTE transforms, view chains, stored procedures with dynamic SQL, dbt model chains, and cross-database ETL, each with the column-level lineage graph explained.\",\n            \"author\": {\n                \"@type\": \"Organization\",\n                \"name\": \"Gudu Software\",\n                \"url\": \"https:\\\/\\\/www.gudusoft.com\\\/\"\n            },\n            \"publisher\": {\n                \"@type\": \"Organization\",\n                \"name\": \"Gudu Software\",\n                \"url\": \"https:\\\/\\\/www.gudusoft.com\\\/\"\n            }\n        },\n        {\n            \"@type\": \"FAQPage\",\n            \"mainEntity\": [\n                {\n                    \"@type\": \"Question\",\n                    \"name\": \"What is a data lineage diagram?\",\n                    \"acceptedAnswer\": {\n                        \"@type\": \"Answer\",\n                        \"text\": \"A data lineage diagram is a directed graph in which nodes are tables, views, or columns and edges show how data flows between them through SQL transformations. Column-level diagrams draw one edge per source-column-to-target-column relationship, annotated with the function or clause that created it.\"\n                    }\n                },\n                {\n                    \"@type\": \"Question\",\n                    \"name\": \"Can I reproduce these examples myself?\",\n                    \"acceptedAnswer\": {\n                        \"@type\": \"Answer\",\n                        \"text\": \"Yes. Paste any of the SQL snippets into the free SQLFlow visualizer at sqlflow.gudusoft.com, pick the matching dialect, and the interactive diagram appears in seconds. The cloud edition has a free tier.\"\n                    }\n                },\n                {\n                    \"@type\": \"Question\",\n                    \"name\": \"What is the difference between direct and indirect lineage?\",\n                    \"acceptedAnswer\": {\n                        \"@type\": \"Answer\",\n                        \"text\": \"Direct lineage means data flows from a source column into a target column, for example SUM(orders.amount) feeding total_amount. Indirect lineage means a column influences the result without appearing in it, such as a WHERE filter or JOIN key. SQLFlow tracks both as separate, toggleable edge types.\"\n                    }\n                },\n                {\n                    \"@type\": \"Question\",\n                    \"name\": \"Can lineage be extracted from stored procedures and dynamic SQL?\",\n                    \"acceptedAnswer\": {\n                        \"@type\": \"Answer\",\n                        \"text\": \"Yes, but it needs a procedural parser. SQLFlow has dedicated parsers for Oracle PL\\\/SQL and SQL Server T-SQL, traces lineage through parameters and temp tables, resolves dynamic SQL built inside procedures, and draws the procedure-to-procedure call graph.\"\n                    }\n                },\n                {\n                    \"@type\": \"Question\",\n                    \"name\": \"Do open-source tools handle these examples?\",\n                    \"acceptedAnswer\": {\n                        \"@type\": \"Answer\",\n                        \"text\": \"Partially. Libraries like sqllineage and sqlglot parse individual queries well and handle simple INSERT-SELECT and CTE cases. The examples worth testing before you commit involve procedural code with dynamic SQL, indirect lineage as a separate edge type, and stitching a multi-dialect estate into one interactive graph.\"\n                    }\n                },\n                {\n                    \"@type\": \"Question\",\n                    \"name\": \"Which SQL dialects do these examples work with?\",\n                    \"acceptedAnswer\": {\n                        \"@type\": \"Answer\",\n                        \"text\": \"SQLFlow ships 39 dialect-specific parsers, including Snowflake, BigQuery, Redshift, Databricks, Oracle, SQL Server, PostgreSQL, MySQL, Teradata, Hive, Spark SQL, and Trino. Each dialect is parsed by its own grammar rather than a generic ANSI one.\"\n                    }\n                }\n            ]\n        }\n    ]\n}<\/script>","protected":false},"excerpt":{"rendered":"<p>A data lineage example shows, for a concrete piece of SQL, which source tables and columns feed which output columns, and through which transformations the data passes along the way. The six worked examples below cover the situations data engineers actually hit: a plain INSERT-SELECT, a multi-CTE transform, a chain of views, a stored procedure with dynamic SQL, a dbt model chain, and a cross-database ETL flow. Each example gives the SQL, then describes exactly what the lineage graph looks like for it. Every example on this page is reproducible: paste the SQL into the free SQLFlow lineage visualizer and\u2026<\/p>","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":[],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":5}},"_links":{"self":[{"href":"https:\/\/www.gudusoft.com\/pt\/wp-json\/wp\/v2\/pages\/6748"}],"collection":[{"href":"https:\/\/www.gudusoft.com\/pt\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.gudusoft.com\/pt\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.gudusoft.com\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.gudusoft.com\/pt\/wp-json\/wp\/v2\/comments?post=6748"}],"version-history":[{"count":0,"href":"https:\/\/www.gudusoft.com\/pt\/wp-json\/wp\/v2\/pages\/6748\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.gudusoft.com\/pt\/wp-json\/wp\/v2\/media?parent=6748"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}