AI 数据血缘解析:自动追踪字段级依赖,不只是表级

发布时间:2026/7/8 14:32:47

AI 数据血缘解析:自动追踪字段级依赖,不只是表级 AI 数据血缘解析自动追踪字段级依赖不只是表级一、为什么表级血缘不够用大家好我是朱大喜。做数据仓库的朋友一定对数据血缘不陌生——A 表经过 SQL 加工变成 B 表这是一条表级血缘。DolphinScheduler 的依赖图、Atlas 的 Lineage 展示大多数工具只能做到这一步。但真实的数据排查场景是这样的老板问报表里的 GMV 字段到底是从哪个源系统哪个字段一路算过来的改了上游的order_amount会影响下游哪些报表这时候表级血缘就彻底失灵了——你只知道 B 表依赖 A 表但不知道是 B 表里的哪一列依赖 A 表的哪一列。字段级血缘Column-Level Lineage才是数据治理的毛细血管级精度。传统做法是靠人手工维护元数据字典但随着 SQL 越来越多、字段越建越复杂手工维护注定跟不上。这时候就需要 AI 上场了——让 LLM 直接解析 SQL 中的字段映射关系。flowchart LR subgraph 表级血缘传统工具 T1[源表 A] -- T2[中间表 B] T2 -- T3[报表表 C] end subgraph 字段级血缘AI 解析 F1[A.order_amount] -- F2[B.gmv_before_tax] F1 -- F3[B.gmv_after_tax] F2 -- F4[C.daily_gmv] F3 -- F5[C.monthly_gmv] F6[A.discount_amount] -- F3 F4 -- F7[最终报表br/total_gmv] F5 -- F7 end style T1 fill:#e3f2fd,stroke:#1565c0 style F1 fill:#e3f2fd,stroke:#1565c0 style F7 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px为什么传统工具做不到字段级血缘因为 Atlas 和 DataHub 的做法是解析 SQL 的 AST抽象语法树来提取表依赖关系——FROM ods_order o JOIN dim_user u这一行就能提取出ods_order → target_table的依赖。但字段映射是o.order_amount o.shipping_fee - o.discount_amount AS gmv这种表达式级别的解析AST 只能告诉你表达式里有order_amount、shipping_fee、discount_amount三个字段但要搞清楚它们和目标字段gmv的映射关系直接透传表达式运算CASE WHEN常规 SQL parser如 sqlparse、ANTLR做不到必须依赖 LLM 的语义理解能力。这也是为什么 AI 在这个场景下不是锦上添花而是雪中送炭。二、让 AI 读懂 SQL 里的前世今生核心思路很简单把 SQL 当作文本让 LLM 做结构化解析。SQL 本身就有明确的语法结构SELECT 子句定义输入 → 输出的映射JOIN 子句定义表之间的关联。这对 AI 来说比自然语言好处理得多。第一步解析单层 SQL 的字段映射-- 这是一段典型的 DWD 层 ETL SQL -- 我们来让 AI 抽取出其中的字段血缘 INSERT INTO dwd.dwd_trade_order_di SELECT o.order_id, o.user_id, -- GMV 计算逻辑订单金额 运费 - 优惠金额 o.order_amount o.shipping_fee - o.discount_amount AS gmv, o.create_time, u.user_name, -- 用户等级翻译用 CASE WHEN 做数值到语义的映射 CASE WHEN u.user_level 1 THEN 普通会员 WHEN u.user_level 2 THEN 银卡会员 ELSE 金卡会员 END AS user_level_name, CURRENT_TIMESTAMP() AS etl_time FROM ods.ods_order_di o LEFT JOIN dim.dim_user_info u ON o.user_id u.user_id WHERE o.status ! 0;把这段 SQL 喂给 AI配上以下 prompt# AI 字段血缘解析的 Prompt 模板 prompt 你是一个 SQL 血缘分析专家。请解析以下 SQL 中的字段级血缘关系。 输出格式要求JSON { lineages: [ { source: {table: 源表名, column: 源字段名}, target: {table: 目标表名, column: 目标字段名}, transform: 转换逻辑描述, transform_type: direct | expression | case_when | function } ] } SQL: {sql_content} 注意 1. 区分直接透传和带转换的血缘关系 2. 每个目标字段都要列出所有参与的源字段 3. CASE WHEN 需要标注为 transform_type: case_when # AI 返回的解析结果示例 lineage_result { lineages: [ { source: {table: ods.ods_order_di, column: order_id}, target: {table: dwd.dwd_trade_order_di, column: order_id}, transform: 直接透传, transform_type: direct }, { source: {table: ods.ods_order_di, column: order_amount}, target: {table: dwd.dwd_trade_order_di, column: gmv}, transform: order_amount shipping_fee - discount_amount, transform_type: expression }, { source: {table: dim.dim_user_info, column: user_level}, target: {table: dwd.dwd_trade_order_di, column: user_level_name}, transform: CASE WHEN user_level1→普通/2→银卡/else→金卡, transform_type: case_when } ] }第二步跨层串联构建完整数据地图单层解析做完后下一步是把多段 SQL 的字段血缘串联成一条完整的链路def build_column_lineage_chain(sql_list, table_ddl_map): 把多段 ETL SQL 的字段血缘串联成完整链路 参数: sql_list: list[str]按执行顺序排列的 SQL 文本列表 table_ddl_map: dict表名 → DDL 的映射用于补充字段定义 返回: dict完整字段血缘图的数据结构 all_lineages [] # 收集所有层的字段映射 for idx, sql in enumerate(sql_list): # 调用 AI 解析每一段 SQL 的字段映射 lineage ai_parse_lineage(sql, table_ddl_map) all_lineages.append({ layer: idx, # 标记是第几层 ETL sql: sql, lineage: lineage }) # 串联逻辑当前层的 source 字段 上一层 target 字段 # 通过字段名 表名做模糊匹配找到上下游关联 chain {} for i in range(len(all_lineages) - 1): current_targets { f{l[target][table]}.{l[target][column]}: l for l in all_lineages[i][lineage] } next_sources { f{l[source][table]}.{l[source][column]}: l for l in all_lineages[i 1][lineage] } # 找交集当前层的输出 下一层的输入 common_keys set(current_targets.keys()) set(next_sources.keys()) for key in common_keys: chain[key] { 上游: current_targets[key], 下游: next_sources[key] } return chain # 使用示例 sql_chain [ ODS → DWD 的 ETL SQL, DWD → DWS 的汇总 SQL, DWS → ADS 的报表 SQL ] result build_column_lineage_chain(sql_chain, {})三、落地构建自动血缘知识库解析出来的血缘数据不能只存在内存里需要持久化到知识库方便随时查询。import sqlite3 from datetime import datetime class ColumnLineageStore: 字段血缘持久化存储 为什么用 SQLite对于血缘这种数据量几万条记录 SQLite 完全够用还能直接在本地查询不需要搭 Neo4j 这样的图数据库。 def __init__(self, db_pathcolumn_lineage.db): self.conn sqlite3.connect(db_path) self._init_tables() def _init_tables(self): 建表源字段 → 目标字段 的映射关系 self.conn.execute( CREATE TABLE IF NOT EXISTS column_lineage ( id INTEGER PRIMARY KEY AUTOINCREMENT, source_table TEXT NOT NULL, -- 源表全名 schema.table source_column TEXT NOT NULL, -- 源字段名 target_table TEXT NOT NULL, -- 目标表全名 target_column TEXT NOT NULL, -- 目标字段名 transform TEXT, -- 转换逻辑描述 transform_type TEXT, -- direct|expression|case_when|function etl_layer INTEGER, -- ETL 层级1ODS,2DWD,3DWS,4ADS updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ) # 建立联合索引追加上游改了什么快速找到影响的下游 self.conn.execute( CREATE INDEX IF NOT EXISTS idx_source ON column_lineage(source_table, source_column) ) self.conn.execute( CREATE INDEX IF NOT EXISTS idx_target ON column_lineage(target_table, target_column) ) self.conn.commit() def upsert_lineage(self, lineage_list): 批量写入血缘数据使用 INSERT OR REPLACE 实现幂等 for l in lineage_list: self.conn.execute( INSERT OR REPLACE INTO column_lineage (source_table, source_column, target_table, target_column, transform, transform_type, etl_layer, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) , ( l[source][table], l[source][column], l[target][table], l[target][column], l[transform], l[transform_type], l.get(etl_layer, 1), datetime.now().isoformat() )) self.conn.commit() def query_downstream_impact(self, table, column): 查询某个源字段影响了哪些下游字段 这是最常用的查询上游要改了看看影响范围多大 return self.conn.execute( SELECT target_table, target_column, transform, transform_type FROM column_lineage WHERE source_table ? AND source_column ? ORDER BY etl_layer , (table, column)).fetchall() def query_upstream_source(self, table, column): 查询某个目标字段来自哪些源字段 用于回答报表这个字段到底是怎么算的 return self.conn.execute( SELECT source_table, source_column, transform, transform_type FROM column_lineage WHERE target_table ? AND target_column ? , (table, column)).fetchall()sequenceDiagram participant Dev as 数据开发 participant AI as AI 解析引擎 participant Store as 血缘知识库 participant BI as 业务方 Dev-AI: 提供 ETL SQL DDL AI-AI: 解析字段映射关系 AI-Store: 写入 column_lineage 表 Store--Dev: 解析完成 BI-Store: 改了 order_amount 影响哪些报表 Store-Store: 递归查询下游字段br/source→target→下一层source→... Store--BI: 完整影响链路 报表列表 Note over Store: 持续更新每次 ETL 变更后自动刷新四、AI 解析的几个关键陷阱在实际落地中有一些 SQL 写法会让 AI 的字段血缘解析失败或出错需要特别注意陷阱 1SELECT * 无法追踪-- ❌ AI 无法解析 SELECT * 的具体字段 -- 必须展开为明确的字段列表 SELECT * FROM ods.ods_order_di; -- ✅ 明确列出所有字段 SELECT order_id, user_id, order_amount FROM ods.ods_order_di;陷阱 2子查询中的字段别名-- ❌ 子查询中的别名和外部引用混杂 SELECT a.gmv FROM ( SELECT order_amount shipping_fee AS gmv FROM orders ) a; -- ✅ 这种情况 AI 一般能处理但需要验证 -- 如果内部还有嵌套子查询建议拆成多段 SQL陷阱 3UNION ALL 导致来源复杂-- UNION ALL 让一个目标字段有多个来源 -- AI 需要识别出每条分支的字段来源 INSERT INTO dw.daily_stats SELECT user_id, amount FROM table_a UNION ALL SELECT user_id, amount FROM table_b; -- 来源不一致处理 UNION ALL 的血缘时需要在 prompt 里强调分别标注每条分支的来源。为什么 SELECT * 是字段血缘的死敌不是 AI 看不懂——AI 能猜出SELECT * FROM orders会把 orders 表的所有字段映射到目标表。问题是 orders 表有 50 个字段DDL 里标的类型是INT但实际数据已经在业务逻辑里被当成状态码用了 3 年字段名叫status实际存的可能是1待支付/2已取消/3已完成。AI 只能解析到orders.status → target.status, direct但不知道这 3 个状态值的业务含义。明确写CASE WHEN status 1 THEN 待支付 ...才能让血缘分析带有业务语义不只是机械的字段映射。字段血缘的目标不只是哪个字段更是这个字段代表了什么。 踩坑提醒AI 解析的准确率约 85%但坏的那 15% 会误导你。最常见的失败场景是多层嵌套子查询和内联函数如LATERAL VIEW EXPLODE。建议给 AI 加一个置信度打分——对直接透传的字段映射置信度 0.95对表达式计算的 0.8对复杂 CASE WHEN 和子查询的 0.6~0.7。低于 0.7 的血缘关系标黄让人工二次确认。INSERT OR REPLACE做幂等写入有个坑如果源字段和目标字段的 pair 不变但transform描述变了比如改了口径你 REPLACE 进去的内容不会触发任何变更通知。更好的做法是加一个version字段每次写入同一 pair 时 version 1然后用触发器或定时任务找出版本号变了但没说清楚变更原因的记录。不要用图数据库Neo4j做血缘存储除非你的血缘超过 10 万条关系。SQLite 的两个联合索引(source_table, source_column)和(target_table, target_column)对 5 万条以内的血缘查询已经能实现毫秒级响应了。Neo4j 的运维成本是 SQLite 的 10 倍部署、备份、查询语言学习在数据量没到图遍历的必要性之前轻量方案永远是最优解。五、总结字段级血缘是数据治理从知道表依赖到知道字段传递的关键一跃。有了它改上游字段前可以一键评估影响范围看报表时能追溯到原始数据来源数据口径的争议也有据可查。AI 在这个场景里的价值被大大低估了——它不是替代元数据管理平台而是解放了手工维护元数据的人力。传统做法是每个新表上线都要手动登记字段映射AI 的做法是扫描 SQL 自动生成。当然现阶段 AI 解析的准确率大约在 85% 左右复杂的嵌套子查询和动态 SQL 还是会出错。我的建议是AI 生成 人工校验把 AI 当初稿助手关键表的核心字段血缘由人来最终确认。这样既保证效率又不丢准确性。—— 朱大喜数据血缘不是画图是追责。

相关新闻