先看一段生产环境里真实的 SQL。它的目标很清晰:把每个账户内所有重叠的时间区间合并起来。语法完全正确,查询也能跑出预期结果。可六个月后,需求变了——合并条件从“只要有重叠就合并”改成“区间相隔不超过 3 天才合并”。这时候,这段 SQL 就几乎等于作废。修改它的难度不亚于从头重写,因为你得把窗口函数的计算过程在脑子里重新跑一遍,才能搞清楚改哪里、怎么改。

这段 SQL 用到了 `MAX() OVER` 和 `SUM() OVER` 的累计求和技巧,还用上了 `ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING`——绝大多数开发者在写它的时候,恐怕都得翻着文档对着例子才能敲出来。从执行引擎的角度看,它精妙且高效;但从阅读者的角度看,它几乎把所有业务逻辑都压缩进了语法结构里。你在子查询里看到一个 `prev_max`,一层层嵌套里有一个 `gid`,可这些字段对应的真实业务含义——“上一个区间的结束日”“分组编号”——完全需要读者自己去推断。代码跑起来没毛病,可读起来,理解成本约等于重写。

打开网易新闻 查看精彩图片

这就是 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 的嵌套结构天然对注释不友好。一句注释夹在多层括号之间,不但不能帮助理解,反而会让视觉结构更加混乱。当注释变成了需要二次解读的噪音,它的可信度就彻底崩塌了。实践里,很多团队干脆放弃了给复杂 SQL 加注释,因为“注释不可信”已经成为共识。

更棘手的是,AI 辅助编码非但没能缓解这个困境,反而可能加剧。现在越来越多人用 AI 生成 SQL——对着聊天框写一段需求描述,AI 吐出一段正确的查询,跑通、提交,这件事就结束了。可你提交到仓库里的是最终那段 SQL,你不会把提问用的 prompt 一起存进去。当下一个需求变更到来,接手的同事——甚至六个月后的你自己——重新打开这段 SQL 时,眼前只剩下一组布满窗口函数和子查询的语句。当初为什么这样写?是基于什么业务假设?这些信息全部丢失。你要么重新询问当时的业务方,要么再次从头推导逻辑,付出的认知成本几乎等同于重写。

这种困境的根源在于,我们一直沿用“代码为第一载体”的开发方法。SQL 文件里留下的是计算过程,但真正驱动这些计算的业务规则,却只在当初的对话或口述中存在过,从未被沉淀。于是,每一次维护都变成了一次考古。而“代码即文档”(Code-as-Documentation)的思路试图翻转这个范式:让业务逻辑本身成为代码组织的主线,把“为什么这样做”和“怎么做”放在同等重要的位置。它不否认 SQL 的执行力,但要求业务规则必须被显式表达,不能再埋藏在嵌套结构和条件判断里。

这一思路和 AI 时代的开发节奏尤其匹配。我们正进入一个“工程师定义意图、机器生成实现”的阶段,如果意图没有被持久化,那么机器生成的实现就只是不可逆的一次性产出。当前用自然语言对话生成 SQL 的方式,天然割裂了意图与实现。想要让生成物可延续、可迭代,就必须把意图以结构化或半结构化的方式留在代码中,形成自解释的单元。业务规则定义在哪里,计算逻辑就如何组织,两者同步演进,才算真正把“代码即文档”落到了 SQL 开发上。

回到开头那段合并区间的 SQL。如果六个月的间隔不是 3 天,而是 7 天或 30 天,又或者需要支持按不同区域采用不同合并粒度,这种场景在保险、金融、物流领域极为常见。可按照现有的开发模式,每一次调整都意味着重新咀嚼窗口函数的运行逻辑,重新验证分组编号是否正确。如果企业长期依赖这种“一锤子”式的 SQL 资产,维护成本将随着需求的叠加指数级增长。要让 SQL 长期可维护,必须让它的编写者意识到:代码的读者,不是六个月后的机器,而是六个月后的人类——很可能就是自己。与其每次从头推导,不如从一开始就把业务语义暴露在最表层。

“代码即文档”并不要求你抛弃 SQL 或改用某种新语言。它要求的只是一项意识转换:在写下每一层子查询、每一个条件字段时,都要问自己一句——这个计算动作代表什么业务规则?半年后,不翻会议记录,我还能认出它吗?如果答案存疑,那么现在的写法就需要重构。这个问题的答案,决定了你的 SQL 资产是随时间增值,还是随时间贬值。