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。

參考資料