AI 时代,代码即文档的 SQL 编写方案 一段“能跑但没法改”的 SQL先看这段生产环境里的 SQL目标是“合并每个账户内所有重叠的时间区间”SELECT account_id, MIN(start_date) AS start_date, MAX(end_date) AS end_date FROM ( SELECT account_id, start_date, end_date, prev_max, SUM(CASE WHEN start_date prev_max OR prev_max IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY account_id ORDER BY start_date) AS gid FROM ( SELECT account_id, start_date, end_date, MAX(end_date) OVER ( PARTITION BY account_id ORDER BY CASE WHEN account_id IS NOT NULL THEN 1 END, start_date ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING ) AS prev_max FROM acc ) t1 ) t2 GROUP BY account_id, gid这段 SQL 用到了窗口函数 MAX()OVER 和 SUM() OVER 的累加技巧还用了 ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING 这种大多数人要查文档才能确认的写法。语法完全正确能跑出结果。但半年后需求变了比如“合并条件从‘重叠’改成‘间隔不超过 3 天也算合并’”改起来难度极大基本等于重写。因为你得从头理解这段 SQL 的逻辑在脑子里重新执行一遍窗口函数的计算过程才能确定改哪里、怎么改。代码能跑但看懂它的代价和重新写一遍差不多。这就是 SQL 开发的常态代码是写给机器执行的不是写给人类阅读的。为什么 SQL 的“文档化”特别难SQL 是声明式语言告诉数据库“要什么”而不是“怎么做”。这带来的问题是业务逻辑被编码在语法结构里而不是显式表达出来。一个窗口函数 LAG(amount,1) OVER(PARTITION BY customer_id ORDER BY month) 背后可能对应着“计算上个月交易额”这个业务概念一段 SUM(CASE WHEN ... THEN 1 ELSE 0 END) OVER (...) 的累加技巧背后可能对应着“按条件分段”这个逻辑但这些业务语义在 SQL 中都是隐含的读者需要自己去推断每一段语法“到底在干什么”更糟的是注释解决不了这个问题。代码改了注释不一定会改久而久之注释比代码更不可信。而且 SQL 的嵌套结构天然抗拒注释注释插在多层括号之间readability 只会更差。AI 编程也解决不了这个问题。用 AI 生成了一段 SQL跑通了提交了。但不会把 AI 的提示词留在代码仓库里留下的只有最终 SQL。下次需求变了还是得面对同一段 SQL重新理解它的逻辑。即便把当初给 AI 写的 spec 也留下了AI 也不能保证再次生成同样的 SQLLLM 是不确定的。改了 spec 让 AI 重新生成生成的 SQL 可能完全不同还得重新审计一遍相当于重写而不是“修改”。所以AI 编程并没有改变“文档与代码脱节”这个根本问题除非把 spec 和 SQL 做成一体的让它俩永远配对改 spec 就自动改 SQL。“代码即文档”是个理想 SQL 让这个理想变得更加遥不可及。这里要澄清一个常见的误解“代码即文档”不等于“在代码里写很多注释”。注释是辅助性的真正的“代码即文档”意味着三件事代码的结构本身就表达了逻辑——看代码就像看文档一样清晰不需要额外解释代码和文档是同一份东西——不存在“代码改了文档没改”的问题新人接手时读代码就能理解业务——不需要额外培训不需要前任留下口头交代在 SQL 领域这意味着 SQL 本身应该具备自描述性每一句代码都在清晰地告诉读者“这一步在做什么”而不是“这一段语法实现了什么”。但原生 SQL 做不到这一点多层嵌套随处可见可读性极差。所以解法是让 spec 同时可以阅读和执行spec 即文档、spec 即代码。SQLazy 的解法workflow 即文档SQLazy 的做法是不直接写 SQL而是用 workflow 描述逻辑步骤。每个步骤就是一个原子操作步骤的组合就是完整的业务逻辑。还是上面那个“合并重叠区间”的例子。用 SQLazy 的 workflow 写步骤操作和含义1sort account_id, start_date2compute end_date[:-1] max as prev_max3segment condition start_date prev_max as gid4summarize start_date min, end_date max这几行 workflow 几乎可以当自然语言来读排序、计算之前最大的结束日期、按条件分段、汇总取最早和最晚。大部分操作不用专门学习就能猜出含义只有 segment 稍微需要解释意思是 按条件把数据分成不同的组但结合后面的可视化分步执行看一眼中间结果就立刻明白了。在线运行本例SQLazy这份可编译的 workflow 本身同时也是文档每一步都有明确的业务含义。sort 就是排序compute 就是计算辅助值segment 就是按条件分组summarize 就是汇总步骤的顺序就是思考的顺序。先排序、再计算、再分段、最后汇总和你手工处理这个问题的思路完全一致不需要额外的注释来解释“这段 SQL 在干什么”。workflow 的每一行已经说清楚了半年后需求变了打开 workflow 直接改对应步骤。代码和文档是一份东西不是两份。这才是“代码即文档”的真正落地。再举一个例子按账户分组活动间隔超过 1 小时则重置序列号。用 SQLazy 的 workflow 写ValueAnchorStatementt2numEventssort account_number asc, dt asct3segment condition ((dt[-1] elapse 3600 second) dt) partition account_number as grpcompute # as seq partition account_number, grp三行 workflow业务逻辑一目了然“超过 1 小时就重新编号”。对应的 SQL 如果手写至少需要嵌套两层窗口函数还要处理 LAG 的时间差计算。但有了 workflow你不需要关心 SQL 怎么写编译器替你搞定。编译器保证文档和代码永不脱节传统模式下文档和代码分离靠人工同步而人工同步很容易出错。文档写完了代码改了没同步或者代码改完了文档忘了更新。SQLazy 的模式是workflow文档→ 编译器 → SQL代码。前面的 workflow 编译成 SQL可切换不同数据库文档是源头代码是编译产物。只要 workflow 不变生成的 SQL 就不会变workflow 改了SQL 自动重新生成。不存在“文档改了代码没改”或“代码改了文档没改”的问题因为它们是一体两面。更换数据库时只需切换目标选项从 MySQL 换成 Oracle从 PostgreSQL 换成 Snowflake编译器自动生成对应方言的 SQL。workflow 不用改文档不用重写。“代码即文档”在 SQL 领域一直是个难以实现的理想。没有 SQLazy 这种 workflow 机制代码和文档永远是两个东西。你写了 SQL还得另外写文档解释它在干什么文档和 SQL 之间的同步只能靠人工而人工同步就会出错。SQLazy 改变了这件事把复杂的 SQL 拆成清晰的步骤步骤本身就是文档编译器保证代码与文档永远一致。这不是在 SQL 外面包一层壳而是改变了 SQL 的开发范式从“写一段机器读的代码”变成“写一套人读的逻辑让机器去翻译”。workflow 是给人看的SQL 是给数据库跑的两者由编译器保证一致不需要人工同步。下次你打开一个三个月前写的 SQL 文件发现自己完全看不懂的时候不妨想想如果当初写的是 workflow现在还会这么痛苦吗