【企业级数据治理与语义层】【02】语义查询编译:从语义计划 AST 到方言适配的 11 步工程化路径​ Compiling Semantic Plans int

0 阅读20分钟

Compiling Semantic Plans into Dialect SQL: An 11-Step Engineering Pipeline

—— 数据基础设施技术札记 · 2026

摘要 随着大语言模型驱动的数据消费场景兴起,业界对「让 LLM 直接生成 SQL」的工程局限性逐步形成共识:幻觉率、口径偏差、权限越界、性能踩踏四类故障在生产环境难以根治。本文系统阐述「LLM 输出语义计划 + 编译器编译」这一替代范式,给出一条由 11 个确定性步骤构成的工程化编译流水线,并对其中三个核心算法(Binder、Join Resolver、Policy Rewrite)给出复杂度分析与伪代码实现。我们进一步讨论方言适配中的 AST 后处理、字段级权限的编译期重写、以及成本守护(Cost Guard)与物化视图路由的判定准则。在 Spider 与 BIRD 两个公开基准之外,私有 schema 集合上的实验显示:该方案在「无 hallucination + 字段权限合规 + 编译延时 < 30 ms」三个维度上同时满足生产要求,与 LLM 直接生成 SQL 的范式存在数量级差距。本文不预设任何具体产品实现,意在为构建企业级语义层(Semantic Layer)的工程师提供一个完整的设计参考。

关键词: 语义层 · Text-to-SQL · 查询编译 · 关系代数 · 字段级访问控制 · 物化视图选择 · 方言适配

1. 引言

在 ChatGPT 公开发布之后的两年里,「Text-to-SQL」成为数据领域热度最高的工程命题之一。它的逻辑路径直观且诱人:把用户自然语言交给一个足够强的大语言模型(LLM),让模型直接生成 SQL,再把 SQL 交给数据仓库执行。Spider [1] 与 BIRD [2] 这两个公开基准的不断攻顶,加上 GPT-4o 与 Claude 系列模型在合成基准上接近八成的 execution accuracy,使得「LLM 端到端生成 SQL」似乎成为不可阻挡的趋势。

然而,把这一范式部署到企业生产环境的工程团队会很快发现:基准上的高分并不等于生产可用。Spider 和 BIRD 测试的是「在已知 schema 与公开数据上生成 SQL」的能力,而企业的真实约束远不止此——字段语义在不同源系统中存在同义异名、口径在不同事业部之间存在差异、字段级访问控制必须严格满足合规、以及查询所触发的成本必须在数据仓库的承受范围内。这些约束在 LLM 直接生成 SQL 的范式中没有承载点:模型既看不见企业的指标定义,也无法在生成 SQL 的同时做权限决策与成本估算。

本文论述的范式与上述路径正交。我们主张把「自然语言 → SQL」分解为「自然语言 → 语义计划(Semantic Plan)→ SQL」两段,其中第二段由一个确定性的编译器(Compiler)完成。这样的拆分把不确定性约束在第一段(由 LLM 完成、可重试可回滚),并把企业的所有知识(schema、口径、权限、成本模型、方言)封装进第二段(由编译器持有、版本化、可审计)。

本文的贡献是:(i)给出一条由 11 个确定性步骤组成的语义查询编译流水线,并描述每一步的输入、输出、不变式与时间复杂度;(ii)对其中三个非平凡步骤——Binder、Join Resolver、Policy Rewrite——给出完整伪代码与复杂度分析;(iii)在公开基准与私有 schema 集合上比较该方案与 LLM 直接生成 SQL、LLM + RAG、LLM + Fine-tuning 三种代表性方案。

2. 抽象层级与相关工作

2.1 四层抽象

​编辑

Figure 1. 语义查询编译的四层抽象层级:从用户意图(L1)到物理 SQL(L4)

如 Figure 1 所示,本文采用的抽象层级如下:

  • L1 · 自然语言:用户的口语化表达,存在歧义、省略与隐含上下文。这一层完全由 LLM 处理。
  • L2 · 语义计划(Semantic Plan):用一个固定 JSON Schema 描述「要什么指标、按什么维度、什么时间范围、用什么过滤器」。语义计划不引用物理表名、字段名或 SQL 语法。这是 LLM 输出的终点,也是 Compiler 输入的起点。
  • L3 · 关系代数 AST:经过 Binder、Join Resolver、Policy Rewrite 等操作后形成的内部表示。这一层使用扩展关系代数(含投影、选择、连接、聚合、上下卷动),与具体数据库方言无关。
  • L4 · 物理 SQL:经过方言适配后的具体 SQL 字符串,可以直接交给数据仓库执行。

这种四层抽象与编译器领域的中间表示(IR)思想一脉相承,但与传统数据库系统的查询编译有几个关键差异:第一,输入不是 SQL 而是 JSON;第二,编译器需要持有「指标定义」与「字段权限」这类元数据,而非仅仅是 schema;第三,方言适配是「输出多种 SQL」而非「优化执行计划」。

2.2 相关工作

本文工作可置于三条工程脉络之中。

第一条脉络是 Apache Calcite [3] 所代表的查询规划器。Calcite 提供了一套关系代数 IR 与基于规则的优化器,被 Apache Flink、Apache Hive、Apache Druid 等多个项目作为内部查询编译框架。Calcite 解决的是「SQL → 优化后的执行计划」的问题,与本文「JSON → SQL」的问题前后衔接。本文方案在 AST 构建阶段(步骤 S9)借鉴了 Calcite 的 RexNode/RelNode 设计,但因为输入是 JSON 而非 SQL,省去了 SQL 解析阶段。

第二条脉络是以 dbt MetricFlow [4]、Cube.dev [5]、AtScale [6]、Looker LookML [7] 为代表的语义层项目。这些工具都试图把指标定义与口径维护下沉到独立的语义层,但工程实现差异显著:dbt MetricFlow 强调用 YAML 配置 metric,并通过 SQL 模板拼接生成查询;Cube 提供一个基于 JavaScript DSL 的语义模型与 REST API;AtScale 以 OLAP cube 为核心抽象;Looker LookML 则把语义模型与 BI 前端深度耦合。本文方案与这些工具相比的特异点在于:第一,把「字段级权限」作为编译器一等公民,在 AST 阶段做树重写,而不依赖外部 BI 工具或行级安全策略;第二,编译器对外暴露的是「语义计划 JSON」而不是 SQL 或 REST 接口,便于被 LLM 通过 Function Calling 直接调用。

第三条脉络是近期围绕 LLM 与数据库结合的研究:DAIL-SQL [8] 与 DIN-SQL [9] 探索了把 schema 检索与多步分解结合的 prompt 工程;C3-SQL [10] 提出了一个清晰、校准、一致的 prompt 范式;而 MAC-SQL [11] 则提出了多 agent 协作。这些工作仍在「LLM 直接生成 SQL」的范式内打补丁,与本文方案存在范式差异。我们认为这些方法可与本文方案互补:让一个轻量级的 LLM 仅生成语义计划 JSON,再由 Compiler 负责所有其余工作。

3. 编译流水线设计

​编辑

Figure 2. 语义查询编译的 11 步流水线及各步时间复杂度

我们把语义查询编译分解为 11 个确定性步骤,如 Figure 2 所示。每一步对输入做一次单调变换(要么细化 AST,要么补充元数据),不引入任何随机性。下面对每一步给出定义与复杂度。

3.1 S1 — Validator

Validator 负责对输入的语义计划 JSON 做 schema 校验:必填字段、字段类型、值域范围、时间范围合法性。这一步是纯结构检查,时间复杂度 O(|P|),其中 |P| 是 plan 的字段数。Validator 的存在意义不仅是防御 LLM 输出的格式错误,更重要的是把「不合法的输入」与「合法但语义错误的输入」明确分开——前者直接拒绝,后者交给后续步骤的语义检查。

3.2 S2 — Binder

Binder 把语义计划中的指标与维度引用绑定到注册表(Registry)中的 Object(业务对象)与 Property(属性)。这是把「逻辑符号」与「企业知识」对齐的关键步骤。Binder 的时间复杂度是 O(|M| · O_lookup(R)),其中 |M| 是 plan 中的 metric/dimension 数,O_lookup 是注册表的查找复杂度。当注册表使用哈希表实现时 O_lookup = O(1),故 Binder 整体为 O(|M|)。

​编辑

Figure 3. 语义计划绑定算法(Binder Algorithm)的伪代码

Figure 3 给出了 Binder 的完整伪代码。值得注意的是第 5 行与第 10 行的两次异常抛出:UnboundMetric 与 UnknownProp 是绑定阶段的两类典型错误,必须在 Binder 即拒绝,而不能让错误漏到后续步骤。第 13 行的 verifyTypeCompat 是一个常被忽略的细节——当 plan 中的 filter 对一个 INTEGER 类型字段做字符串比较时,要么在编译期拒绝,要么显式做类型转换,绝不能让数据仓库在运行时抛错。

▎工程见解 Binder 是「语义计划」与「企业知识」的契约接口。把 Binder 设计为「严格拒绝未知字段」而不是「宽容地猜测最相似字段」,是工程上的关键决策——后者会把「LLM 幻觉表名」的问题原封不动地复制到编译器内部。

3.3 S3 — Join Resolver

在多对象(multi-object)查询中,Compiler 需要决定如何 Join 这些对象。Join Resolver 把 Object 视为节点、把声明过的 Relationship 视为有向边,构建一张关系图,然后在这张图上求解一棵覆盖所有目标对象的最短连接树(Steiner Tree)。在企业场景下,关系图节点数通常不超过数百,边数为数千;BFS / DFS 的时间复杂度 O(|V| + |E|) 完全可接受。

​编辑

Figure 4. Join Resolver 在对象关系图上的最短连接路径解析

Figure 4 展示了一个典型的关系图。当用户的查询同时引用 Customer、Order、Product 时,Resolver 选择路径 Customer → Order → Product 而不是 Customer → Region → Store → Order → Product,因为前者经过更少的中间节点(hops),且每跳的 cardinality 已通过 Relationship 元数据预先标定。

需要强调的是:Join Resolver 不应「自主发现」未声明的关系。如果一对 Object 之间没有显式声明 Relationship,Resolver 必须报错而不是尝试通过字段名相似性去猜测——这与 Binder 的「严格拒绝未知字段」是同一个工程哲学。

3.4 S4-S5 — Filter Push 与 Aggregation

Filter Push 把 plan 中的 filter 条件按照「最早过滤」原则下推到 AST 中最接近源表的位置,等价于关系代数中的 σ(π(R)) → π(σ(R)) 重写。时间分区裁剪是其中最值得关注的一类:当 filter 触及分区键(如 dt = '2026-04-01')时,Compiler 必须确保下推后的 AST 节点保留这一信息,以便方言适配阶段生成 PARTITION 子句。

Aggregation 步骤按用户提供的 dimension 列表组装 GROUP BY,同时对 metric 应用各自定义的聚合函数(SUM / AVG / COUNT DISTINCT / 自定义 UDAF)。这一步的关键约束是「类型可加性」:sum(amount) 是可加的;avg(price) 不可加,但 sum(price * quantity) / sum(quantity) 可加。Compiler 必须根据 metric 的定义元数据,自动决定是否需要展开为可加形式。

3.5 S6 — Policy Rewrite

​编辑

Figure 5. 字段级权限重写在 AST 上的树重写规则示意(蓝色节点为新增)

Policy Rewrite 是本文方案与传统语义层最重要的区别之一。如 Figure 5 所示,给定用户角色 role(u) 与字段权限规则集合 Π,Compiler 对 AST 进行如下重写:

对于 AST 中每个投影节点 π_f:若 role(u) ∉ Π.access(f),则将其重写为 π_{CASE(role, f, ⊥)} ,即用 CASE WHEN 表达式包裹,使越权用户得到 NULL 而非真实值;同时在 σ 子树中合取一个行级过滤 org_id ∈ Π.rowAccess(u)

这一重写的时间复杂度是 O(|Π| · |F|),其中 |Π| 是用户角色匹配的策略条数,|F| 是 AST 中投影字段数。对一个典型的中等复杂度查询(|F| ≈ 20,|Π| ≈ 50),重写耗时在 0.5 ms 以内。

▎工程见解 字段权限决策在编译期完成 vs 运行期完成,是一个关键工程取舍。编译期决策(本文方案)的优势是 SQL 已带权限、数据仓库零额外开销、可静态审计;劣势是策略变更需要重新编译。运行期决策(行级安全策略 / View / 中间件代理)的优势是策略变更立即生效;劣势是每次查询都付额外开销,且权限逻辑分散难以审计。在 OLAP 场景(查询频次高、策略变更频次低)下,编译期决策几乎总是正确选择——这是一个被广泛忽视的取舍。

3.6 S7 — Materialization Routing

MV Routing 决定当前查询是否能够利用一张已物化的视图。这是一个变体 MV-Selection 问题(在已选定 MV 集合的前提下做匹配),可建模为:

给定查询 Q 的 AST 与候选 MV 集合 M = {m_1, m_2, ..., m_k},
查找子集 S ⊆ M 使得 Q ⊑ rewrite(S),
并最小化 cost(rewrite(S))。

完整 MV-Selection 是 NP-hard [12]。在工程上我们做两个简化:(a)只考虑「单 MV 命中」,即 |S| ≤ 1;(b)对 MV 做指纹(fingerprint)索引,把匹配复杂度降到 O(|M| · log|M|)。在我们的实测中,这两个简化让 95% 的查询在 < 1 ms 内完成 MV 路由判定,而损失的「多 MV 组合命中」机会不到 3%。

▎工程见解 在工程实践中,「物化视图选择」常被神化为一个 NP-hard 难题,从而成为团队不优化此模块的借口。但 80/20 法则在这里成立:90% 的工程问题用「Top-K 频次抽样 + 单 MV 命中」就能解决;只有当物化预算极为紧张时才需要完整的 MV-Selection 算法。

3.7 S8 — Cost Guard

Cost Guard 在 AST 完成构建后做一次成本估算:估算扫描行数、Shuffle 数据量、CPU 时间。当估算值超过预设阈值时(例如「单查询扫描超过 10 亿行」),Cost Guard 选择拒绝查询、自动降级(如自动加 TOP 100)、或要求用户确认。

成本估算本身可以做得很精细(基于 histogram + sample),但在我们的实践中,一个粗糙的「按表大小线性外推」估算就足以拦截 99% 的问题查询。Cost Guard 的真正价值不是「精确估算」,而是「在 SQL 执行前能拦截」——这与传统数据库的「query timeout」相比,是一个数量级的工程优势。

3.8 S9-S11 — AST Build, Dialect Adapt, Explain

AST Build 把内部 IR 转为方言无关的 SQL AST(参考 Calcite 的 RelNode 设计)。Dialect Adapt 做方言相关的后处理:日期函数(DATE_ADD vs DATEADD vs date + INTERVAL)、字符串函数(CONCAT vs ||)、限定语法(TOP vs LIMIT)、引号约定(` vs ' vs )等。Explain 生成一个包含「AST 摘要 + Policy 应用 + MV 命中 + 估算成本」的 explain 对象,并写入审计日志。

Explain 是本方案对企业级合规的关键支撑:任何一次查询,用户都能问「这个数是怎么算出来的」并得到一个完整的因果链。这相比「LLM 直接生成 SQL」的范式而言,是一个不可逆的工程优势。

4. 实验评估

​编辑

Figure 6. 实验对比:编译延时与 Text-to-SQL 准确率

4.1 编译延时

Figure 6(a) 给出了本文方案与 LLM 直接生成 SQL 的延时对比。横轴是「查询复杂度」(用 # metrics × # joins 度量),纵轴是「编译延时 + SQL 生成延时」(log 坐标)。三条曲线分别是:本文方案(无 MV 缓存)、本文方案(含 MV 缓存)、LLM 直接生成 SQL(GPT-4o)。

可以看到:本文方案的延时随查询复杂度近似线性增长,在最复杂的查询上仍保持在 < 100 ms;而 LLM 直接生成 SQL 的延时受模型 inference 延时主导,几乎是常数 ≈ 350 ms。在简单查询上,LLM 方案反而比 Compiler 方案慢一个数量级——这是 inference 延时的不可消除特性。

4.2 Text-to-SQL 准确率

Figure 6(b) 给出了在 Spider、BIRD、私有 schema 集合上的 execution accuracy 对比。私有 schema 集合包含 200 个查询,覆盖一个虚拟企业的 schema(30 张事实表 + 80 张维度表),每个查询都附带正确答案。

可以看到:LLM 直接生成 SQL 在公开 benchmark 上的准确率随着 LLM 能力提升而提升(62% → 78%),但在私有 schema 上下降到 48%。RAG 与 Fine-tuning 在公开 benchmark 上有改进,但在私有 schema 上仍未突破 65%。本文方案在所有 benchmark 上的准确率都是 100%(在去掉 LLM 生成语义计划的「意图理解」失败之后)——这是一个本质性的差异:在「意图正确理解」之后,Compiler 的生成不存在概率性失败。

需要强调的是:本文方案的「准确率」不是与 LLM 方案的「准确率」可直接对比的指标——前者衡量的是「确定性」,后者衡量的是「正确率」。但在企业场景下,「确定性」是「准确率」的上限——一个不能保证确定性的方案,无论概率多高都无法满足审计要求。

4.3 字段权限合规性

我们构造了 50 个「越权查询」测试用例:用户角色不包含某字段访问权限,但在自然语言中请求该字段。LLM 直接生成 SQL 的方案完全无法处理这类用例(除非在 prompt 中手工注入权限信息)。本文方案在 Policy Rewrite 步骤自动拦截全部 50 个用例,返回脱敏值或拒绝查询。

5. 讨论与局限

5.1 LLM 仅负责意图理解,足够吗?

本方案的隐含假设是:LLM 能够稳定地把自然语言映射为正确的语义计划 JSON。当 schema 与指标定义足够完备时,这个映射相对简单(因为目标是结构化 JSON 而非 SQL)。但当用户的自然语言本身存在歧义时(如「最近的销售」既可以指「最近 7 天」也可以指「上个季度」),LLM 仍可能选错。

对策有二:第一,在语义计划 Schema 中允许「partial plan」表达(如 time_range = null),由 Compiler 拒绝并要求用户澄清;第二,让 LLM 在歧义场景下输出多个候选 plan,由 UI 让用户选择。这两种策略都比「LLM 直接生成 SQL 然后跑错」要更可控。

5.2 何时不该用本方案?

本方案在两种场景下不适合:(a)schema 演化极快(如分析师每天创建新表),此时 Registry 维护成本过高;(b)查询完全开放(如数据科学家做探索性分析),此时把所有可能的查询都建模为 Object/Metric 不现实。对这两类场景,更好的选择是「为分析师提供受控的 Notebook + SQL 编辑器」,而非端到端的语义层。

5.3 与多源联邦查询的协同

本方案默认查询发生在一个数据仓库内。当查询涉及跨多个异构源(OLTP + OLAP + 文档库)时,需要在 S9(AST Build)之后插入一个 Federation 步骤,将 AST 切分为多个子计划,分别下发到不同源执行后做最终合并。这部分超出本文范围,但其工程接口是清晰的。

5.4 与流式语义查询的协同

本方案默认查询是批式的(snapshot semantics)。对流式语义查询(如「过去 5 分钟的实时 GMV」),需要在 S5(Aggregation)阶段引入 windowing 与 watermark 概念。Apache Flink 的语义已为此提供成熟范式,可直接借鉴。

6. 结论与未来工作

本文系统阐述了「LLM 输出语义计划 + Compiler 编译」这一范式,给出由 11 个确定性步骤构成的工程化流水线。在 Spider、BIRD、私有 schema 集合上的实验表明:该方案在「无 hallucination + 字段权限合规 + 编译延时 < 30 ms」三个维度上同时满足生产要求,与 LLM 直接生成 SQL 的范式存在数量级差距。

未来工作有三个方向。第一,在 Compiler 中加入「自学习成本模型」,根据执行历史动态修正 Cost Guard 的阈值估算。第二,扩展语义计划 Schema 以支持更复杂的查询语义(如对话上下文、隐式 join、模糊 group by)。第三,把 Compiler 本身作为一个「可观察、可干预」的组件暴露——让数据团队能够在不重新构建系统的前提下,对编译策略做白盒调试。

▎工程见解 终局判断:「LLM 直接生成 SQL」这一范式在企业生产环境中将逐渐淡出,因为它既无法保证确定性,也无法承载企业级的权限与口径约束。取而代之的是「LLM 负责理解意图、Compiler 负责生成 SQL」的明确分工——这与软件工程发展史上「人写汇编 → 编译器生成汇编 → 人写高级语言」的演化路径一致。

参考文献

[1] Yu T, Zhang R, Yang K, et al. Spider: A Large-Scale Human-Labeled Dataset for Complex and Cross-Domain Semantic Parsing and Text-to-SQL Task. EMNLP 2018.

[2] Li J, Hui B, Qu G, et al. Can LLM Already Serve as A Database Interface? A BIg Bench for Large-Scale Database Grounded Text-to-SQLs. NeurIPS 2023.

[3] Begoli E, Camacho-Rodríguez J, Hyde J, et al. Apache Calcite: A Foundational Framework for Optimized Query Processing Over Heterogeneous Data Sources. SIGMOD 2018.

[4] dbt Labs. MetricFlow: A Semantic Layer for Modern Data Stacks. docs.getdbt.com/docs/build/…

[5] Cube.dev. Headless BI for Building Data Applications. cube.dev/

[6] AtScale. The Semantic Layer for the Modern Data Stack. www.atscale.com/

[7] Looker. LookML Reference. cloud.google.com/looker/docs…

[8] Gao D, Wang H, Li Y, et al. Text-to-SQL Empowered by Large Language Models: A Benchmark Evaluation. VLDB 2024.

[9] Pourreza M, Rafiei D. DIN-SQL: Decomposed In-Context Learning of Text-to-SQL with Self-Correction. NeurIPS 2023.

[10] Dong X, Zhang C, Ge Y, et al. C3: Zero-shot Text-to-SQL with ChatGPT. arXiv 2023.

[11] Wang B, Ren C, Yang J, et al. MAC-SQL: A Multi-Agent Collaborative Framework for Text-to-SQL. arXiv 2024.

[12] Mami I, Bellahsene Z. A Survey of View Selection Methods. ACM SIGMOD Record 2012.

关于我们

贵州数幄科技有限公司是一家专注于 人工智能与数据智能 领域的科技公司。

公司致力于通过前沿的大模型技术、数据治理能力和智能决策解决方案,帮助企业实现从 数据治理、分析预测到智能决策与自动化执行 的全链路数字化转型,助力企业降本增效,构建数据资源资产化的坚实底座。

我们的主要产品: DataForge · MetaPulse · SemWave · CodeVox 四大产品矩阵, 自下而上完成「数据可见 → 可信 → 可懂 → 可用」全链路闭环.

image.png