SQL 像英语是个善意的错误
我们知道,SQL 很像英语,简单的 SQL 语句直接可以作为英语读。除了 SQL 外,其它主要程序设计语言都没有这样,语法中就算有英语单词也仅仅是作为某些概念或操作的助记符而已,写出来的是形式化的程序语句 (statement) 而不是英语句子(sentence)。而 SQL 不同,它会把整个句子写成符合英语习惯的形式,还会补充很多不必要的介词,比如 FROM 作为语句的运算主体却被写到后面,GROUP 后面要写一个多余的 BY。
为什么会这样?很容易想到的理由是希望非程序设计人员也能使用。用户只要会读写英语,就可以写出 SQL 来查询数据。这显然是个善意的初衷,但结果却不尽如人意。绝大多数业务人员只会用 SQL 写非常简单的查询,而对于这类查询,现在有强大的 BI 软件能提供更为便捷直观的可视化界面来协助,并不需要直接手写语句,这个设计初衷就失去意义。反过来, 经常使用 SQL 做运算的仍然是程序员,SQL 还是一种编程语言,像不像英语对于程序员理解并没有多大差别,反而会带来不小的困难。
事实上,SQL 是一种语法非常严格的语言,语句中任何一点不合规的地方就会被数据库拒绝,使用者必须认真学习并遵守其语法规则,这和其它程序设计语言并没什么两样。而自然语言真正的优势在于具有模糊性,可以一定程度接受不严格的语法,但 SQL 并没有支持这一点,在发明 SQL 那个年代也实现不了这个特性。
像英语的好处体现不了,反而有不少坏处,将语法设计得像自然语言,看起来容易掌握,其实恰恰相反。
贴近自然语言带来的主要坏处是非过程性。程序逻辑一般是分步执行的,用变量记录中间结果,供后面的步骤使用。但自然语言不是这样,两句话之间的引用关系靠少量几个固定的代词维系,不精确也不方便。所以会把针对同一个主语的动作尽量拼到一句话中,这样就不必借助代词了。在 SQL 中的对应表现就是在一条语句中配有多个动作,SELECT、WHERE、GROUP 这些本来是无关的动作,在其它程序语言中通常会设计成多个函数,但在 SQL 中都会设计成语句的子句。还有,像 WHERE 和 HAVING 根本是一个意思,只对针对的对象不同,在拼到一个句子中就要采用两个词以示区别,让人费解(很多初学者会对 HAVING 很晕)。
实在一句话无法描述的复杂情况,在自然语言中就会使用从句了。这在 SQL 中的表现就是子查询,还可能出现多层嵌套的子查询,这种现象在其它程序设计语言中是不常见的。而且,子查询也要像自然语言,每次都要有个 SELECT…FROM,就会让人觉得非常啰嗦,代码变得长。
分步是降低理解和执行难度的有效办法。本来挺简单分几步能做到的事情,如果不分步就会很绕。可以想象,如果老师要求小学生做应用题时只能列一个算式完成,小朋友们会多么苦恼(当然,不乏一些聪明孩子搞得定)。
比如我们要找出销售额超过平均值两倍的客户,自然思维方式就是先计算出销售额的平均值,再找出销售额超这个值两倍的客户,两个语句完成。而 SQL 的写法就需要用子查询写成更长的一句。这个例子还算好懂,只有两层,一般自然语言的从句用来描述两层关系的理解难度还可以接受,但实际复杂的查询涉及到三五层的比比皆是,严重增加理解难度。
不提倡分步,就会导致单句 SQL 很长。程序员面临的复杂 SQL 语句,很少以行计,经常是以 K 计。而同样的 100 行代码,分成 100 个语句还是只有 1 个语句,其复杂度完全不是一个级别的。这种代码理解起来非常困难,好不容易写出来,过两个月后自己都读不懂,而且太长不分步的单句非常难以调试,开发周期也更长。
关于过程性,业界有个说法,SQL 是声明式语言,用户只要关心要什么,而不必关心怎么做,数据库会自动找解决方案,这档的语言不需要支持过程性。我们在前面已经批判过这个说法。
数据库厂商应该是也发现了 SQL 缺乏过程性的问题,所以后来增加了 CTE 语法来弥补,相当于提供了可以命名的中间变量。存储过程也相当于可以分步执行 SQL,有了分支循环甚至子程序。结果还是要回到过程式语言的老路,那还不如直接就设计成这样。
对于程序语言来说,好的分步计算机制带来的易用性要远远超过长得像自然语言。
英文版