{"id":3177,"date":"2026-04-05T23:00:38","date_gmt":"2026-04-05T15:00:38","guid":{"rendered":"https:\/\/www.dpriver.com\/blog\/?p=3177"},"modified":"2026-04-05T23:00:40","modified_gmt":"2026-04-05T15:00:40","slug":"why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it","status":"publish","type":"post","link":"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/","title":{"rendered":"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)"},"content":{"rendered":"\n<p>If you work with enterprise databases, you already know: stored procedures are everywhere. They hold critical business logic, enforce data transformations, and move data between tables in ways that no view or SELECT statement can capture. PL\/SQL packages in Oracle, T-SQL procedures in SQL Server, PL\/pgSQL functions in PostgreSQL &#8212; these are the workhorses of data processing in large organizations.<\/p>\n\n\n\n<p>And yet, when you fire up a SQL lineage tool and point it at your database, those stored procedures are often quietly ignored. The lineage diagram shows tables and views connected by SELECT and INSERT statements, but the procedural code that actually drives data flow? It is either missing entirely or represented as a black box with no column-level detail.<\/p>\n\n\n\n<p>This is not a minor gap. In regulated industries, auditors want to know exactly how data moves from source to target. In migration projects, you need to understand what a stored procedure does before you can rewrite it. And for impact analysis, a schema change that breaks a stored procedure can cascade into production failures that no lineage tool warned you about.<\/p>\n\n\n\n<p>Let us look at why this happens, what the evidence looks like in practice, and what a proper solution requires.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why Tools Skip Stored Procedures<\/h2>\n\n\n\n<p>The core challenge is architectural. Most SQL parsers are built to handle <strong>declarative SQL<\/strong> &#8212; SELECT, INSERT, UPDATE, DELETE, CREATE VIEW. These statements describe <em>what<\/em> data to retrieve or modify. Stored procedures, on the other hand, contain <strong>procedural logic<\/strong> &#8212; IF\/ELSE branches, loops, exception handlers, cursor operations, dynamic SQL construction, and vendor-specific control flow.<\/p>\n\n\n\n<p>There are three main reasons tools struggle:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Preprocessing vs. Native Parsing<\/h3>\n\n\n\n<p>Many lineage tools use a preprocessing approach: they extract the SQL statements embedded inside a procedure body and feed them into a standard SQL parser one at a time. This loses context. A variable assigned in one statement and used three lines later in an INSERT becomes invisible. A cursor that iterates over a result set and inserts rows conditionally cannot be resolved without understanding the control flow.<\/p>\n\n\n\n<p>Native parsing means the parser understands the procedural language itself &#8212; DECLARE blocks, BEGIN\/END scoping, cursor declarations, loop constructs, exception handlers &#8212; as first-class syntax. This is significantly harder to build, which is why most tools take the shortcut.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. Control Flow Complexity<\/h3>\n\n\n\n<p>A stored procedure is not a single data flow &#8212; it is a program with branches. Consider a PL\/SQL procedure that uses <code>BULK COLLECT<\/code> to fetch 10,000 rows into a collection, then uses <code>FORALL<\/code> to insert them into a target table with an exception handler that logs failures to an error table. The lineage from source columns to target columns passes through collection variables, loop indices, and conditional branches. Tracking column-level lineage through this requires something closer to program analysis than SQL parsing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. Vendor-Specific Syntax<\/h3>\n\n\n\n<p>Each database vendor has its own procedural language with unique syntax. Oracle&#8217;s PL\/SQL has packages, nested procedures, autonomous transactions, and pipelined table functions. SQL Server&#8217;s T-SQL has TRY\/CATCH, table variables, the GO batch separator, and MERGE with OUTPUT. PostgreSQL&#8217;s PL\/pgSQL has PERFORM (execute and discard), RAISE (logging), RETURN QUERY, and dollar-quoted strings. Supporting all of these at production quality means maintaining separate, deep grammars for each dialect.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Evidence From the Field<\/h2>\n\n\n\n<p>The pattern is consistent across the ecosystem. Without naming and shaming, here is what we see:<\/p>\n\n\n\n<p><strong>Open-source metadata platforms<\/strong> have acknowledged procedural SQL gaps. One major platform has had open issues for over five months related to stored procedure lineage extraction, with community members requesting support for PL\/SQL and T-SQL procedure parsing that the platform&#8217;s SQL parser cannot handle.<\/p>\n\n\n\n<p><strong>Python-based SQL transpilers<\/strong> that are popular in the dbt ecosystem work well for SELECT statement analysis but struggle with procedural constructs. T-SQL&#8217;s GO batch delimiter, complex PL\/SQL blocks with nested BEGIN\/END, and vendor-specific syntax like Oracle&#8217;s <code>EXECUTE IMMEDIATE<\/code> are known limitations. The transpiler approach works for the 80% case (queries and views) but hits a wall with procedural code.<\/p>\n\n\n\n<p><strong>Large-scale migration tools at Fortune 500 companies<\/strong> have encountered this directly. One major technology company&#8217;s open-source migration utility (issue #2098) documented how their SQL parser could not propagate column-level lineage tokens through procedural logic, and the team discussed replacing their parser entirely due to dialect limitations in handling stored procedures.<\/p>\n\n\n\n<p><strong>Database migration tools<\/strong> &#8212; both commercial (cloud provider schema conversion tools) and open-source (Oracle-to-PostgreSQL converters) &#8212; frequently fail on specific PL\/SQL patterns. <code>BULK COLLECT<\/code> combined with <code>FORALL<\/code>, which is one of the most common performance optimization patterns in Oracle, is a known pain point. The migration tool either errors out or produces incorrect target code because it does not fully parse the procedural context.<\/p>\n\n\n\n<p><strong>Data catalog platforms<\/strong> have added stored procedure support in their UI &#8212; you can view and edit procedure code &#8212; but the actual <em>parsing<\/em> for lineage extraction is not there. Showing the code is not the same as understanding the data flow within it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What Good Stored Procedure Parsing Looks Like<\/h2>\n\n\n\n<p>To extract accurate column-level lineage from stored procedures, a parser needs to handle the full procedural language, not just the SQL statements embedded in it. Here are three real-world examples that illustrate the complexity.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">PL\/SQL: Package With Cursor Loops and BULK COLLECT<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE OR REPLACE PACKAGE BODY etl_pkg AS\n  PROCEDURE load_customer_summary IS\n    TYPE t_cust_rec IS RECORD (\n      customer_id   customers.customer_id%TYPE,\n      total_amount  NUMBER,\n      order_count   NUMBER\n    );\n    TYPE t_cust_tab IS TABLE OF t_cust_rec;\n    l_batch t_cust_tab;\n\n    CURSOR c_active IS\n      SELECT c.customer_id,\n             SUM(o.amount) AS total_amount,\n             COUNT(o.order_id) AS order_count\n      FROM customers c\n      JOIN orders o ON o.customer_id = c.customer_id\n      WHERE c.status = 'ACTIVE'\n      GROUP BY c.customer_id;\n  BEGIN\n    OPEN c_active;\n    LOOP\n      FETCH c_active BULK COLLECT INTO l_batch LIMIT 5000;\n      EXIT WHEN l_batch.COUNT = 0;\n\n      FORALL i IN 1..l_batch.COUNT\n        MERGE INTO customer_summary cs\n        USING (SELECT l_batch(i).customer_id AS cid,\n                      l_batch(i).total_amount AS amt,\n                      l_batch(i).order_count AS cnt FROM dual) src\n        ON (cs.customer_id = src.cid)\n        WHEN MATCHED THEN\n          UPDATE SET cs.total_amount = src.amt,\n                     cs.order_count = src.cnt,\n                     cs.updated_at = SYSDATE\n        WHEN NOT MATCHED THEN\n          INSERT (customer_id, total_amount, order_count, updated_at)\n          VALUES (src.cid, src.amt, src.cnt, SYSDATE);\n\n      COMMIT;\n    END LOOP;\n    CLOSE c_active;\n  EXCEPTION\n    WHEN OTHERS THEN\n      ROLLBACK;\n      INSERT INTO etl_error_log (procedure_name, error_msg, error_time)\n      VALUES ('load_customer_summary', SQLERRM, SYSDATE);\n      COMMIT;\n      RAISE;\n  END load_customer_summary;\nEND etl_pkg;<\/code><\/pre>\n\n\n\n<p>The lineage here flows from <code>customers.customer_id<\/code>, <code>orders.amount<\/code>, and <code>orders.order_id<\/code> through a cursor, into a PL\/SQL collection variable, through a <code>FORALL<\/code> + <code>MERGE<\/code> statement, and finally into <code>customer_summary.customer_id<\/code>, <code>customer_summary.total_amount<\/code>, and <code>customer_summary.order_count<\/code>. The error path also creates lineage into <code>etl_error_log<\/code>. A parser that only sees the MERGE statement without understanding the cursor and collection context will miss the source columns entirely.<\/p>\n\n\n\n<p><a href=\"https:\/\/www.gudusoft.com\">General SQL Parser<\/a> handles this by parsing the full PL\/SQL package body, resolving variable types through <code>%TYPE<\/code> and record declarations, tracking data flow through <code>BULK COLLECT<\/code> into collection variables, and connecting those variables through the <code>FORALL<\/code> loop to the final MERGE target.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">T-SQL: Procedure With Temp Tables, Dynamic SQL, and TRY\/CATCH<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE PROCEDURE dbo.usp_refresh_product_metrics\n    @CategoryID INT,\n    @StartDate DATE\nAS\nBEGIN\n    SET NOCOUNT ON;\n    BEGIN TRY\n        -- Stage data in temp table\n        SELECT p.product_id,\n               p.product_name,\n               p.category_id,\n               SUM(s.quantity) AS total_qty,\n               SUM(s.quantity * s.unit_price) AS total_revenue\n        INTO #product_staging\n        FROM products p\n        INNER JOIN sales s ON s.product_id = p.product_id\n        WHERE p.category_id = @CategoryID\n          AND s.sale_date &gt;= @StartDate\n        GROUP BY p.product_id, p.product_name, p.category_id;\n\n        -- Apply business rules\n        UPDATE #product_staging\n        SET total_revenue = total_revenue * 0.9\n        WHERE total_qty &gt; 1000;\n\n        -- Merge into target\n        MERGE product_metrics AS tgt\n        USING #product_staging AS src\n        ON tgt.product_id = src.product_id\n        WHEN MATCHED THEN\n            UPDATE SET tgt.product_name = src.product_name,\n                       tgt.total_qty = src.total_qty,\n                       tgt.total_revenue = src.total_revenue,\n                       tgt.last_updated = GETDATE()\n        WHEN NOT MATCHED THEN\n            INSERT (product_id, product_name, category_id,\n                    total_qty, total_revenue, last_updated)\n            VALUES (src.product_id, src.product_name, src.category_id,\n                    src.total_qty, src.total_revenue, GETDATE());\n\n        DROP TABLE #product_staging;\n    END TRY\n    BEGIN CATCH\n        IF OBJECT_ID('tempdb..#product_staging') IS NOT NULL\n            DROP TABLE #product_staging;\n\n        INSERT INTO dbo.error_log (proc_name, error_message, error_time)\n        VALUES ('usp_refresh_product_metrics', ERROR_MESSAGE(), GETDATE());\n\n        THROW;\n    END CATCH\nEND\nGO<\/code><\/pre>\n\n\n\n<p>This procedure creates a temp table via <code>SELECT INTO<\/code>, modifies it with an UPDATE, then merges into the final target. The column-level lineage must trace <code>products.product_name<\/code> and <code>sales.quantity<\/code> through <code>#product_staging<\/code> and into <code>product_metrics<\/code>. The <code>GO<\/code> batch delimiter, <code>TRY\/CATCH<\/code> error handling, and temp table lifecycle all need to be understood. General SQL Parser resolves the temp table schema from the <code>SELECT INTO<\/code> statement, tracks the UPDATE transformation, and maps columns through the MERGE into the final target &#8212; including the error path to <code>error_log<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">PL\/pgSQL: Function With PERFORM, RAISE, and RETURN QUERY<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE OR REPLACE FUNCTION analytics.refresh_user_engagement(\n    p_days_back INTEGER DEFAULT 30\n)\nRETURNS TABLE (\n    user_id BIGINT,\n    engagement_score NUMERIC,\n    segment TEXT\n)\nLANGUAGE plpgsql\nAS $$\nDECLARE\n    v_threshold NUMERIC;\n    v_count INTEGER;\nBEGIN\n    -- Cleanup old data\n    PERFORM analytics.archive_old_engagement(p_days_back * 2);\n\n    SELECT AVG(login_count)::NUMERIC INTO v_threshold\n    FROM analytics.user_activity\n    WHERE activity_date &gt;= CURRENT_DATE - p_days_back;\n\n    INSERT INTO analytics.engagement_daily (\n        user_id, engagement_score, segment, calc_date\n    )\n    SELECT ua.user_id,\n           (ua.login_count * 0.4 + ua.page_views * 0.3\n            + ua.actions_taken * 0.3)::NUMERIC AS engagement_score,\n           CASE\n             WHEN (ua.login_count * 0.4 + ua.page_views * 0.3\n                   + ua.actions_taken * 0.3) &gt; v_threshold THEN 'high'\n             ELSE 'standard'\n           END AS segment,\n           CURRENT_DATE\n    FROM analytics.user_activity ua\n    WHERE ua.activity_date &gt;= CURRENT_DATE - p_days_back\n    ON CONFLICT (user_id, calc_date) DO UPDATE\n    SET engagement_score = EXCLUDED.engagement_score,\n        segment = EXCLUDED.segment;\n\n    GET DIAGNOSTICS v_count = ROW_COUNT;\n    RAISE NOTICE 'Processed % user engagement records', v_count;\n\n    RETURN QUERY\n    SELECT ed.user_id, ed.engagement_score, ed.segment\n    FROM analytics.engagement_daily ed\n    WHERE ed.calc_date = CURRENT_DATE;\nEND;\n$$;<\/code><\/pre>\n\n\n\n<p>Here, <code>PERFORM<\/code> calls another function (with side effects), <code>RAISE NOTICE<\/code> is PostgreSQL-specific logging, and <code>RETURN QUERY<\/code> makes this a table-returning function whose output can feed into other queries. The lineage traces <code>user_activity.login_count<\/code>, <code>user_activity.page_views<\/code>, and <code>user_activity.actions_taken<\/code> through computed expressions into <code>engagement_daily.engagement_score<\/code> and <code>engagement_daily.segment<\/code>, and also through the function&#8217;s return type. General SQL Parser understands all of these PL\/pgSQL constructs natively.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Column-Level Lineage Through Stored Procedures, Not Around Them<\/h2>\n\n\n\n<p>The key distinction is whether a tool extracts lineage <em>through<\/em> stored procedures or just <em>around<\/em> them. Working around them means treating the procedure as a black box &#8212; you know it reads from table A and writes to table B, but you do not know which specific columns are involved or how they are transformed. Working through them means the parser understands the procedural logic well enough to trace individual columns from source to target, including through variables, cursors, collections, temp tables, and control flow branches.<\/p>\n\n\n\n<p><a href=\"https:\/\/www.gudusoft.com\">General SQL Parser<\/a> takes the &#8220;through&#8221; approach. It parses the full procedural language for each supported dialect, builds an internal representation of variable declarations, assignments, and data flow, and resolves column-level lineage across procedural boundaries. When you feed it a PL\/SQL package or a T-SQL stored procedure, the output is not just &#8220;this procedure touches these tables&#8221; &#8212; it is a complete column-to-column lineage map that shows exactly how data flows from source to target.<\/p>\n\n\n\n<p>You can see this in action with <a href=\"https:\/\/sqlflow.gudusoft.com\">SQLFlow<\/a>, which visualizes the lineage extracted by General SQL Parser. Upload a stored procedure and you will see the column-level connections rendered as a data flow diagram, with each transformation step visible.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Try It Yourself<\/h2>\n\n\n\n<p>If stored procedure lineage is a gap in your current tooling, here is what you can do:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Visualize your procedures<\/strong>: Upload a stored procedure to the <a href=\"https:\/\/sqlflow.gudusoft.com\">SQLFlow online demo<\/a> and see the column-level lineage diagram it produces.<\/li>\n<li><strong>Test your edge cases<\/strong>: Try the patterns that break other tools &#8212; BULK COLLECT + FORALL, temp tables in T-SQL, PL\/pgSQL RETURN QUERY. These are the cases where parser quality shows.<\/li>\n<li><strong>Analyze in your IDE<\/strong>: If you use VS Code, <a href=\"https:\/\/gudu-sql-omni.gudusoft.com\/index.html\">Gudu SQL Omni<\/a> brings column-level lineage, ER diagrams, and impact analysis directly into your editor &#8212; 100% offline, 34 dialects, free trial. <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=gudusoftware.gudu-sql-omni\">Install from the VS Code Marketplace<\/a>.<\/li>\n<li><strong>Evaluate against your codebase<\/strong>: General SQL Parser supports Oracle PL\/SQL, SQL Server T-SQL, PostgreSQL PL\/pgSQL, MySQL stored procedures, and DB2 SQL PL. Details and downloads are at <a href=\"https:\/\/www.gudusoft.com\">gudusoft.com<\/a>.<\/li>\n\n\n\n<p>Stored procedures are not going away. If anything, the push toward database-side logic for performance and security is growing. A lineage solution that skips them is a lineage solution with blind spots &#8212; and in an era of data governance and regulatory compliance, blind spots are not acceptable.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most SQL lineage tools silently skip stored procedures. Learn why PL\/SQL, T-SQL, and PL\/pgSQL procedural code breaks typical parsers, and how General SQL Parser extracts column-level lineage through stored procedures &#8212; not just around them.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[14,25,8],"tags":[125,26,122,124,121,123],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":5}},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v19.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)<\/title>\n<meta name=\"description\" content=\"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)\" \/>\n<meta property=\"og:description\" content=\"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/\" \/>\n<meta property=\"og:site_name\" content=\"SQL and Data Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-04-05T15:00:38+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-05T15:00:40+00:00\" \/>\n<meta name=\"author\" content=\"James\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"James\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#organization\",\"name\":\"SQL and Data Blog\",\"url\":\"https:\/\/www.dpriver.com\/blog\/\",\"sameAs\":[],\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png\",\"contentUrl\":\"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png\",\"width\":251,\"height\":72,\"caption\":\"SQL and Data Blog\"},\"image\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#website\",\"url\":\"https:\/\/www.dpriver.com\/blog\/\",\"name\":\"SQL and Data Blog\",\"description\":\"SQL related blog for database professional\",\"publisher\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.dpriver.com\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/\",\"url\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/\",\"name\":\"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)\",\"isPartOf\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#website\"},\"datePublished\":\"2026-04-05T15:00:38+00:00\",\"dateModified\":\"2026-04-05T15:00:40+00:00\",\"description\":\"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)\",\"breadcrumb\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.dpriver.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)\"}]},{\"@type\":\"Article\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/\"},\"author\":{\"name\":\"James\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04\"},\"headline\":\"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)\",\"datePublished\":\"2026-04-05T15:00:38+00:00\",\"dateModified\":\"2026-04-05T15:00:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/\"},\"wordCount\":1503,\"publisher\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#organization\"},\"keywords\":[\"column lineage\",\"data lineage\",\"PL\/SQL\",\"SQL parser\",\"stored procedures\",\"T-SQL\"],\"articleSection\":[\"gsp\",\"sql\",\"SQL language\"],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04\",\"name\":\"James\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g\",\"caption\":\"James\"},\"sameAs\":[\"http:\/\/www.dpriver.com\"],\"url\":\"https:\/\/www.dpriver.com\/blog\/author\/james\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)","description":"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/","og_locale":"en_US","og_type":"article","og_title":"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)","og_description":"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)","og_url":"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/","og_site_name":"SQL and Data Blog","article_published_time":"2026-04-05T15:00:38+00:00","article_modified_time":"2026-04-05T15:00:40+00:00","author":"James","twitter_card":"summary_large_image","twitter_misc":{"Written by":"James","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Organization","@id":"https:\/\/www.dpriver.com\/blog\/#organization","name":"SQL and Data Blog","url":"https:\/\/www.dpriver.com\/blog\/","sameAs":[],"logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png","contentUrl":"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png","width":251,"height":72,"caption":"SQL and Data Blog"},"image":{"@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"WebSite","@id":"https:\/\/www.dpriver.com\/blog\/#website","url":"https:\/\/www.dpriver.com\/blog\/","name":"SQL and Data Blog","description":"SQL related blog for database professional","publisher":{"@id":"https:\/\/www.dpriver.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.dpriver.com\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/","url":"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/","name":"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)","isPartOf":{"@id":"https:\/\/www.dpriver.com\/blog\/#website"},"datePublished":"2026-04-05T15:00:38+00:00","dateModified":"2026-04-05T15:00:40+00:00","description":"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)","breadcrumb":{"@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.dpriver.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)"}]},{"@type":"Article","@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/#article","isPartOf":{"@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/"},"author":{"name":"James","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04"},"headline":"Why Most SQL Lineage Tools Skip Stored Procedures (And What You Can Do About It)","datePublished":"2026-04-05T15:00:38+00:00","dateModified":"2026-04-05T15:00:40+00:00","mainEntityOfPage":{"@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/why-most-sql-lineage-tools-skip-stored-procedures-and-what-you-can-do-about-it\/"},"wordCount":1503,"publisher":{"@id":"https:\/\/www.dpriver.com\/blog\/#organization"},"keywords":["column lineage","data lineage","PL\/SQL","SQL parser","stored procedures","T-SQL"],"articleSection":["gsp","sql","SQL language"],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04","name":"James","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g","caption":"James"},"sameAs":["http:\/\/www.dpriver.com"],"url":"https:\/\/www.dpriver.com\/blog\/author\/james\/"}]}},"_links":{"self":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts\/3177"}],"collection":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/comments?post=3177"}],"version-history":[{"count":2,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts\/3177\/revisions"}],"predecessor-version":[{"id":3180,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts\/3177\/revisions\/3180"}],"wp:attachment":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/media?parent=3177"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/categories?post=3177"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/tags?post=3177"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}