【数据蒋堂】第 16 期:SQL 像英语是个善意的错误

sjjt-16

我们知道,SQL 很像英语,简单的 SQL 语句直接可以作为英语读。除了 SQL 外,其它主要程序设计语言都没有这样,语法中就算有英语单词也仅仅是作为某些概念或操作的助记符而已,写出来的是形式化的程序语句 (statement) 而不是英语句子(sentence)。而 SQL 不同,它会把整个句子写成符合英语习惯的形式,还会补充很多不必要的介词,比如 FROM 作为语句的运算主体却被写到后面,GROUP 后面要写一个多余的 BY。

为什么会这样?很容易想到的理由是希望非程序设计人员也能使用。用户只要会读写英语,就可以写出 SQL 来查询数据。这显然是个善意的初衷,但结果却不尽如人意。绝大多数业务人员只会用 SQL 写非常简单的查询,而对于这类查询,应用程序常常都有更为便捷直观的可视化界面来协助,并不需要直接手写语句,这个设计初衷就失去意义。反过来, 经常使用 SQL 做运算的仍然是程序员,SQL 还是一种编程语言,像不像英语对于程序员理解并没有多大差别,反而会带来不小的困难。

事实上,SQL 是一种语法非常严格的语言,语句中任何一点不合规的地方就会被解释器拒绝,使用者必须认真学习并遵守其语法规则,这和其它程序设计语言并没什么两样。而自然语言真正的优势在于具有模糊性,可以一定程度接受不严格的语法,但 SQL 并没有支持这一点,在发明 SQL 那个年代也实现不了这个特性。

像英语的好处没有体现,坏处却很严重,将语法设计得像自然语言,看起来容易掌握,其实恰恰相反。

贴近自然语言带来的主要坏处是非过程性。程序逻辑一般是分步执行的,用变量记录中间结果,供后面的步骤使用。但自然语言不是这样,两句话之间的引用关系靠少量几个代词维系,不够用且不精确,所以更习惯的做法是把尽量多的任务写在一句话中,复杂情况下就会大量使用从句。在 SQL 中的表现就是一句话中配有多个动作,SELECT、WHERE、GROUP 都拼进去,比如 WHERE 和 HAVING 其实就是一个意思,却要采用两个词以示区别,而查询需求复杂时就会出现多层嵌套的子查询。这种现象在其它程序设计语言中是不常见的。

分步是降低理解和执行难度的有效法门,本来挺简单分几步能做到的事情,如果不分步就会很绕。比如要找出销售额超过平均值两倍的客户,自然思维方式就是先把出销售额的平均值,再找出销售额超这个值两倍的客户,两个语句完成。而 SQL 的写法就需要用子查询写成更长的一句。这个例子还算好懂,只有两层,一般自然语言的从句用来描述两层关系的理解难度还可以接受,但实际复杂的查询涉及到三五层的比比皆是,严重增加理解难度。

不提倡分步,就会导致单句 SQL 很长。程序员面临的复杂 SQL 语句,很少以行计,经常是以 K 计。而同样的 100 行代码,分成 100 个语句还是只有 1 个语句,其复杂度完全不是一个层面的。这种代码理解起来非常困难,好不容易写出来,过两个月后自己都读不懂,而且太长不分步的单句非常难以调试,开发周期也更长。

关于过程性,业界一直有一个说法:写 SQL 时用户只要关心要什么,而不必关心怎么做,计算机会自动找解决方案,这样语法本身不需要支持过程性。

这完全是胡扯!

SQL 中不必关心数据在物理存储层面(文件系统、内存和硬盘)的动作,但仍然要关心逻辑层面(表和字段)的运算。SQL 语句事实上也在描述运算逻辑,特别是多层嵌套关联的复杂 SQL,就是在指明执行过程。而因为它像英语,反而使得指明复杂过程非常困难。

不过,SQL 不提倡分步,但并非完全不能支持过程计算,后来增加的 CTE 语法就是为了弥补过程性不足。存储过程也相当于分步骤执行 SQL,在外部程序中调用 SQL 也可以实现过程。无论如何,毕竟麻烦了不少,而且常常性能也不佳。

对于程序语言来说,好的分步计算机制带来的易用性要远远超过长得像英语。