Lantide Data
返回博客
数据分析实践

AI 写的 SQL 怎么审?不只看能不能跑的 7 点检查表

AI SQL 成功执行不代表回答正确。本文用 grain、JOIN、filter、NULL、时间、分母与 LIMIT 七点检查表,附最小验证查询。

审 AI 产生的 SQL,不能只看语法成功与结果像不像。至少要核对资料 grain、JOIN cardinality、filter、NULL、时间边界、聚合分母,以及 LIMIT/抽样;每一项都应搭配小型验证查询。SQL 能跑只证明引擎接受它,不证明它回答了业务问题。

这个落差在企业情境尤其明显。2026 年 EntSQL benchmark 收录五个领域、1,066 组中英对照题目,多数需要问题与 schema 之外的内部指标、报表惯例或组织规则;在该研究提供长篇企业文件的英文设定中,最佳受测系统只有 15.9%。这是特定 benchmark、版本与评估方式的结果,不能当成所有 Text-to-SQL 的通用准确率,但它说明「有 schema」不等于「懂口径」。

以下用「计算 2026 年 6 月完成订单的客户平均消费」为例。假设 orders 一列一张订单,order_items 一列一个品项。先别急着看平均数,依序审七点。

1. Grain:结果的一列代表什么?

先用一句话写出每张来源表与结果的 grain:

orders:一列一张订单
order_items:一列一个订单品项
结果:一列一位客户

再检查 key 是否符合假设:

SELECT
  count(*) AS rows,
  count(DISTINCT order_id) AS distinct_orders
FROM orders;

若两者不同,order_id 不是唯一,或资料已有重复。没有 grain,后面的平均、distinct 与 JOIN 都只能靠运气。

2. JOIN cardinality:有没有 fan-out?

一张订单 JOIN 多个品项后,orders.total_amount 会被复制。以下 SQL 语法正确,总额却可能被放大:

SELECT sum(o.total_amount)
FROM orders o
JOIN order_items i USING (order_id);

在 JOIN 前后比较 row count 与 distinct key:

SELECT
  count(*) AS joined_rows,
  count(DISTINCT o.order_id) AS distinct_orders
FROM orders o
JOIN order_items i USING (order_id);

如果只需要确认存在某种品项,可用 EXISTS;如果要算品项金额,先按 order_id 聚合 items,再 JOIN。不要用 SELECT DISTINCT 隐藏尚未理解的 fan-out。

3. Filter:包含与排除条件完整吗?

「完成订单」可能是 paidfulfilled,是否排除退款、测试帐号与内部订单?逐项把自然语言对到 WHERE

WHERE status IN ('paid', 'fulfilled')
  AND is_test = false
  AND refunded_at IS NULL

接着看被排除的分布,而不是只看保留资料:

SELECT status, count(*) AS n
FROM orders
GROUP BY status
ORDER BY n DESC;

状态定义来自业务文件或 owner,不应由模型根据栏名自行推断。

4. NULL:三值逻辑会排掉谁?

WHERE country <> 'TW' 不会保留 country IS NULL 的列;sum(amount) 会忽略 NULL;count(column)count(*) 也不同。先量化:

SELECT
  count(*) AS rows,
  count(customer_id) AS rows_with_customer,
  count(*) FILTER (WHERE customer_id IS NULL) AS missing_customer
FROM orders;

然后明确决定 NULL 是未知、无此值,还是资料错误。COALESCE 是业务处理,不是清理魔法。

5. 时间边界:用半开区间,先说清时区

月份筛选建议使用 [start, end),避免漏掉有时间的小数秒:

WHERE created_at >= TIMESTAMPTZ '2026-06-01 00:00:00+08:00'
  AND created_at <  TIMESTAMPTZ '2026-07-01 00:00:00+08:00'

但这只在 created_at 的型别与来源时区已确认时才成立。还要问:以建单、付款还是完成时间归月?跨日完成算哪一个 cohort?日期写对了,事件选错仍会答非所问。

6. Aggregation 与 denominator:平均的是订单还是客户?

AVG(total_amount) 是平均订单金额,不是「每位客户平均消费」。后者应先按客户聚合,再平均:

WITH customer_spend AS (
  SELECT customer_id, sum(total_amount) AS spend
  FROM orders
  WHERE status IN ('paid', 'fulfilled')
    AND created_at >= TIMESTAMPTZ '2026-06-01 00:00:00+08:00'
    AND created_at <  TIMESTAMPTZ '2026-07-01 00:00:00+08:00'
  GROUP BY customer_id
)
SELECT
  count(*) AS customers,
  avg(spend) AS avg_spend_per_customer
FROM customer_spend;

同时输出 denominator count,Report 不要只留 rate 或 average。COUNT(DISTINCT customer_id) 也不能修复上游定义不清。

7. LIMIT、抽样与排序:结果是全量还是预览?

AI 在探索时常用 LIMIT 100。如果 LIMIT 被留在 CTE 或聚合前,最终结果可能只代表一小部分资料。没有 ORDER BY 的 LIMIT 也不保证是「最新」或「随机」样本。

审查时搜寻 LIMITTABLESAMPLE 与取样条件,确认它们只服务 preview,或在 Report 明确揭露抽样设计。若要 top 10,必须确认排序指标与 tie 处理。

一组最小验证查询,不要只跑主查询

正式 SQL 至少配三种 checkpoint:

  1. 体量:来源与每个主要 filter 前后的 row/distinct key。
  2. 分布:状态、日期、NULL 与极端值分布。
  3. 对帐:选一小段已知资料,手算或与可信报表比较。

例如:

-- Checkpoint:每个 filter 后剩多少订单与客户
SELECT
  count(*) AS orders,
  count(DISTINCT customer_id) AS customers,
  min(created_at) AS min_ts,
  max(created_at) AS max_ts,
  sum(total_amount) AS total_amount
FROM orders
WHERE status IN ('paid', 'fulfilled')
  AND created_at >= TIMESTAMPTZ '2026-06-01 00:00:00+08:00'
  AND created_at <  TIMESTAMPTZ '2026-07-01 00:00:00+08:00';

Checkpoint 的期望不一定是固定数字,也可以是「orders 不少于 customers」「时间不超出 6 月」「NULL customer 为零」。把 expected condition 写出来,才能在资料更新后重跑验证。

Lantide Data 如何让 SQL Review 不脱离分析契约

Lantide Data 采 SQL-first:Plan 说明业务问题、grain、分母与 checkpoints;持久 SQL 分页保存取数逻辑;Report 保存结论与 limitations。Agent 可草拟 SQL,但正式 Project Analysis 由使用者审阅 Plan 后按 Approve & Execute,SQL 与 execution evidence 留在同一分析脉络。流程见 SQL-first 工作流Plan → Execute → Report

持久分页执行后的快取可右键 查看 SQL;上游分页可用 Source Run 依赖重跑与查看 lineage。这让 reviewer 能从 Report 数字回到查询,而不是翻聊天。但 Lantide 不保证 AI SQL 正确:grain、业务状态、时间与排除条件仍需资料 owner/分析师判断;快取也是暂存中间结果,不是永久数仓。

结语:把「能跑」改成「能解释、能验证」

下次审 AI SQL,先不要看最后一格数字。依序写出 grain,检查 JOIN、filter、NULL、时间、denominator 与 LIMIT,再加三类 checkpoint。只有当主查询与验证查询能共同支持同一个业务问题,这段 SQL 才值得进 Report。

参考资料