如果系统分别为 SQL 和 DSL 编写销售额公式、状态过滤和权限处理,两条路线很容易在一次修改后产生差异。双入口的价值,是让不同写法最终使用同一套业务定义和执行规则。
第 4 章的 DSL 和语义 SQL 专题,都要求同一模型、同一时间、同一客户粒度和同一销售额口径。
| 查询要素 | 语义 SQL | Query DSL |
|---|---|---|
| 金额汇总 | SUM(salesAmount) AS totalSales |
columns 中相同表达式 |
| 六月范围 | invoiceDate >= ... AND invoiceDate < ... |
slice 的 [) 区间 |
| 客户粒度 | 按客户 ID 和名称分组 | groupBy 列出两个字段 |
| 排序 | 金额降序,客户 ID 升序 | -totalSales、customerId |
| 返回范围 | LIMIT 3 |
limit: 3 |
下面用简化逻辑计划表示它们共同的含义。这是解释用的步骤,实际内部节点名称由引擎决定:
从 SalesInvoiceQueryModel 取数
→ 应用模型规则和当前用户的数据范围
→ 筛选 2025-06-01 ≤ 开票日期 < 2025-07-01
→ 按客户 ID、名称分组,求销售额合计
→ 按合计降序、客户 ID 升序排序
→ 返回前 3 行
这是逻辑含义,优化器可以调整实际执行顺序,前提是保留语义和权限约束。
flowchart LR
A[语义 SQL] --> B[SQL 解析与名称绑定]
C[Query DSL] --> D[结构解析与字段绑定]
B --> E[共同的语义校验与逻辑计划]
D --> E
E --> F[物理规划与方言编译]
F --> G[执行并返回结果]
M[模型定义与身份上下文] --> E
SQL 前端负责解析语法树,DSL 前端负责读取结构化请求。两个入口随后进入共同的模型、类型、聚合和权限检查,再由执行层规划关联、展开计算,生成底层查询。
第 4 章的 DSL 描述一次语义模型查询;用于分阶段分析的 DSL 还可以组织多个步骤及中间结果。它们的结构和能力不同,不能只因都使用 JSON 风格就互相替换。
CLI 或 API 还需要模型、命名空间、分页等调用信息。调用外壳负责把请求送到正确入口,不等于内部执行计划。
内部表示常称为 IR,用来保存解析、绑定后的查询含义。它可以包含多阶段计算和数据流,通常比公开的单模型请求更丰富。因此,SQL 与 DSL 共用引擎,不要求“所有 SQL 都先翻译成同一份公开 JSON”。
先固定数据快照、模型版本、当前身份和查询参数,再比较两条路线的字段、条件、聚合阶段、结果数值和顺序。
只比较“金额都一样”还不够:客户名称相同可能合并了不同客户,少一个条件也可能恰好不影响当前数据。可以沿用第 2 章的同额发票、月底记录和无明细发票作为边界样例,再加入不同身份的数据范围验证。
在双入口实现中,两种写法引用同一未公开字段,也应受到相同限制。核对目标是查询含义与约束一致,生成的 SQL 文本可以因别名或优化策略而不同。
SQL 容易被熟悉数据库的人理解,也方便对接 SQL 工具,但需要维护解析器、名称绑定、函数与方言支持范围。DSL 的结构便于约束和校验,调用方则要学习字段和表达方式。
共用模型可以减少业务规则的重复维护,解析适配、错误诊断和回归验证仍然需要分别完成。只有确实需要 SQL 客户端或分析人员入口时,才值得承担第二套前端的成本。
统一执行计划把“怎样写查询”与“怎样解释和执行查询”分开。输入可以不同,业务含义、权限约束和核对标准应保持一致。
性能与一致性另设专题,系列顺序在基础篇完成后确定。