05 · 受约束 SQL 生成实现

目标:基于本体推理结果 + 映射,用「模板 + 参数填充」生成 SQL,并做口径校验。LLM 不写 SQL,只填参数。

1. 核心原则

LLM 不写 SQL,只填参数。 SQL 结构由指标模板固化,口径固化在模板里。

2. 指标 SQL 模板(针对账单表 dwd_bill)

public class MetricSqlTemplate {
    // GMV 模板:口径固化(status='paid' AND refunded=false)
    public static final String GMV =
        "SELECT {region_col}, SUM(bill_amount) AS gmv " +
        "FROM dwd_bill " +
        "WHERE status='paid' AND refunded=false " +   // ← 口径固化
        "AND dt BETWEEN '{start_dt}' AND '{end_dt}' " +
        "{region_filter} " +
        "GROUP BY {region_col}";

    // 订单数模板
    public static final String ORDER_COUNT =
        "SELECT SUM(bill_count) AS cnt FROM dwd_bill " +
        "WHERE status='paid' AND dt BETWEEN '{start_dt}' AND '{end_dt}' {region_filter}";
}

3. 参数填充流程

① 本体推理结果:
   - 指标:GMV(等价识别后)
   - 维度展开:华东区 → [上海,江苏,浙江,安徽,福建,江西,山东]
   - 时间:上个月 → 2026-07-01 ~ 2026-07-31
② 选模板:根据指标选 GMV 模板
③ 填参数:
   region_filter = "AND region_id IN (SELECT region_id FROM dim_region WHERE region_name IN ('上海','江苏',...))"
   start_dt = "2026-07-01", end_dt = "2026-07-31"
④ 生成 SQL(确定性拼接,无 LLM 参与)
public String generateSql(String metric, List<String> regions, TimeRange time) {
    String template = selectTemplate(metric);
    String regionFilter = regions.isEmpty() ? "" :
        "AND region_id IN (SELECT region_id FROM dim_region WHERE region_name IN (" +
        regions.stream().map(r -> "'" + r + "'").collect(Collectors.joining(",")) + "))";
    return template
        .replace("{region_col}", "region_id")
        .replace("{region_filter}", regionFilter)
        .replace("{start_dt}", time.start())
        .replace("{end_dt}", time.end());
}

4. 口径校验(公理校验 SQL)

public class CaliberValidator {
    /** 校验生成的 SQL 是否满足指标口径 */
    public boolean validate(String metric, String sql) {
        // 从本体读口径公理(whereClause 数据属性)
        String requiredClause = ontologyService.getWhereClause(metric);
        // GMV 的 requiredClause = "status='paid' AND refunded=false"
        // 校验 SQL 是否包含必需的过滤条件
        for (String clause : requiredClause.split(" AND ")) {
            if (!sql.contains(clause)) return false;
        }
        return true;
    }
}

5. 完整链路

@Service
public class MetricQueryService {
    // ① 等价识别
    String metric = ontologyService.resolveEquivalentMetric("成交额"); // → GMV
    // ② 维度展开
    List<String> regions = ontologyService.expandRegion("华东区");     // → 7省
    // ③ 生成 SQL
    String sql = sqlGenerator.generateSql(metric, regions, time);
    // ④ 口径校验
    if (!caliberValidator.validate(metric, sql)) {
        throw new CaliberException("SQL 违背指标口径");
    }
    // ⑤ 执行(复用 MaxComputeQueryService)
    return maxComputeQueryService.query(sql);
}

6. 关键注意