我不再信 AI 生成的 SQL 了,我现在信这套做法
为什么我不信任 AI 生成的 SQL 了,不是因为它不通,而是因为它太通——能过语法检查、能执行,却可能在不注意的地方漏洞百出。
你有没有遇到过 AI 给的 SQL 通过了语法检查、执行不报错,数据却悄悄错了一行?
能跑,不等于可信
一个看起来很简单的需求:状态流水表用字段 "NewStatus" 记录每个 ID 的状态,每个 ID 都有 "ConfirmationStarted" 和多条 "Closed",要取 "ConfirmationStarted" 之前最近的那条 "Closed"。
CreatedAt |
ID |
NewStatus |
2022-05-25 23:17:44 |
147 |
Active |
2022-05-28 05:59:02 |
147 |
Closed |
2022-06-18 05:59:01 |
147 |
Closed |
2022-06-25 05:59:01 |
147 |
Closed |
2022-07-13 00:02:47 |
147 |
ConfirmationStarted |
2022-08-25 05:59:01 |
147 |
Closed |
2023-04-29 05:59:02 |
1645 |
Closed |
2023-05-08 14:53:34 |
1645 |
ConfirmationStarted |
期望结果:
ID |
CreatedAt |
147 |
2022-06-25 05:59:01 |
1645 |
2023-04-29 05:59:02 |
以 ID=147 为例,在 ConfirmationStarted 之前共有三条 Closed,最后一条 06-25 才是“最近的一条”;08-25 虽也是 Closed 但已在 ConfirmationStarted 之后,不算。
期望结果只有两行:"147→2022-06-25"、"1645→2023-04-29"。其他 Closed 要么太早,要么在 ConfirmationStarted 之后,都不算。
把需求丢给 AI,几秒拿到一段漂亮的 SQL,CTE 套窗口函数,review 时挑不出毛病。上线两周后对账才发现 147 取成了 05-28,数据错了一行,但 SQL 能跑。
乍一看 bug 很难发现:分段逻辑写对了,却把最后的聚合从 MAX 写成了 MIN,把“离 ConfirmationStarted 最近的一条”变成了“最早的一条”,结果在边界数据上悄悄偏了一行。
AI 给的有漏洞的 SQL 长这样:
WITH t2 AS (
SELECT CreatedAt, ID, NewStatus
, 1 + SUM(CASE WHEN NewStatus='ConfirmationStarted' THEN 1 ELSE 0 END)
OVER (PARTITION BY ID ORDER BY ID ASC, CreatedAt ASC ROWS UNBOUNDED PRECEDING) AS seg
FROM mytable
)
SELECT ID, MIN(CreatedAt) AS CreatedAt --这是几处错误之一,应为MAX,取最近的
FROM (
SELECT CreatedAt, ID, NewStatus, seg
FROM t2
WHERE NewStatus='Closed' AND seg=1
) t_3
GROUP BY ID
ORDER BY ID
这就是 AI 直接产终态 SQL 最危险的地方:你能让 AI 重写一段话,却很难让它自己发现逻辑里那一行看不见的偏差。
我们让AI做了最不该做的事
AI 很擅长把口语需求拆成步骤,却不擅长为最终 SQL 的每一个边界条件都给出 100% 保证。它是概率模型,不是编译器。
公开评测显示,即使让最先进的大模型直接把自然语言翻译成可执行 SQL,在复杂查询上的执行准确率也常在六成左右徘徊,远未达到可直接签核的程度,平均三、四次就可能错一次。
旧范式是 "提示词→AI→不确定的终态 SQL",黑盒交付,只能让它整段重猜。
我们需要的新范式应该是 "提示词→AI→规范步骤→编译器→确定性 SQL",每一步可验证。
“跑得通”不算数,“敢签字”才算。
SQLazy,就是这个敢签字的底气,就是规范步骤的编译器,就是新范式的落地工具。
在SQLazy里,这件事只要4步
同样的 "最近一条 Closed",在 SQLazy 里是 4 步,关键是:每一步都能点开看中间结果。
Name |
Anchor |
Statement |
t1 |
mytable |
sort ID, CreatedAt asc |
t2 |
segment condition (NewStatus = "ConfirmationStarted") partition ID as seg |
|
t3 |
filter (NewStatus = "Closed" and seg = 1) |
|
t4 |
summarize CreatedAt max as CreatedAt; group ID |

第 1 步 "sort ID, CreatedAt asc" 先把时序排正。
第 2 步 "segment condition (NewStatus ="ConfirmationStarted") partition ID as seg" 按 "ConfirmationStarted" 分段,"partition ID" 保证每个 ID 独立。分段列的名字是 seg,其中 seg=1 就是目标区间,不用手写 "SUM(CASE WHEN ...) OVER"。

第 3 步 "filter (NewStatus ="Closed"and seg = 1)" 只留目标区间里的 Closed。
第 4 步 "summarize CreatedAt max as CreatedAt; group ID" 每 ID 取最大时间,就是最近一条。
分段对不对,点开 t2 看 "seg" 列就知道;哪步错了就改哪步——错在第 2 步就只改第 2 步,不用推倒重来。编译时选 MySQL 还是 Snowflake,永远同一输入同一输出,不会编造字段。
WITH t2 AS (
SELECT CreatedAt, ID, NewStatus
, 1 + SUM(CASE
WHEN (NewStatus = 'ConfirmationStarted') THEN 1
ELSE 0
END) OVER (PARTITION BY ID ORDER BY ID ASC, CreatedAt ASC ROWS UNBOUNDED PRECEDING) AS seg
FROM mytable
)
SELECT ID, MAX(CreatedAt) AS CreatedAt
FROM (
SELECT CreatedAt, ID, NewStatus, seg
FROM t2
WHERE (NewStatus = 'Closed'
AND seg = 1)
) t_3
GROUP BY ID
ORDER BY ID
这段 SQL 不是 AI 猜的,是编译器按固定规则把 4 步翻译出来的,肯定对,不用审。
当然,简单 CRUD 真没必要用 SQLazy,SQLazy 就是为那种 30 行以上的复杂逻辑准备的,比如带时序、分段、相对位置的逻辑。
把你最可疑的那段AI SQL贴进来
最典型的幻觉是什么?凭空编造不存在的字段或表、把筛选条件安到错的列上、把关联键或聚合口径搞错——SQL 照样能跑,结果早已跑偏,类似的场景人人都曾遇到。
别再陷入“重写提示词→重跑→碰运气”的循环了。把那段最不放心的业务逻辑丢给 SQLazy,拆成一步步可验的流程,并在评论区晒出你的幻觉案例。
AI给逻辑,编译器保落地——零幻觉
