从 SQL 和 Java 的对比理解集合化,SQL 到底比 Java 优势在哪?

同样的数据计算任务,用 SQL 写和用 Java 写,后者常常会长出数倍。代码长不仅仅是写起来很繁琐,也不利于理解整体业务逻辑结构,算法过程都湮没在细节中。
为什么 Java 会比 SQL 长这么多?我们来回答这个问题,并引出程序语言的集合化概念。

首先是针对集合的运算能力,这个很容易理解。
业务逻辑经常面对结构化数据,而结构化数据通常是批量(以集合形式)出现的,要方便计算这类数据,程序设计语言有必要提供足够的集合运算能力。
早期 Java 没有直接提供面向结构化数据的集合运算类库,甚至连象样的结构化数据对象都没有。做个简单计数求和都要写很多行,分组连接这些更是麻烦无比。
SQL 则有比较完善集合运算,如 SUM/COUNT 等聚合运算,WHERE 用于过滤、GROUP 用于分组,写出来的代码就会短小很多。

那么,给 Java 补一些集合运算的库函数不就完了吗?比如,比如说 Java8 之后的 Stream 就有不少,是不是就可以了?
没这么简单!
我们用排序运算举例,在 SQL 中排序简单写成 ORDER BY price 就可以了,你不用关心这个 PRICE 的数据类型。
Java 是一种类型严格的编译语言,同一个函数不能针对不同数据类型工作,就要为不同数据类型分别写一遍排序函数,整数、实数、字符串各自不同,只是麻烦库函数开发者也就罢了,问题是使用者也要指明数据类型,编译器才能找到函数。Java 已经发明了泛型语法来简化写法,但代码中仍然会有一堆尖括号,看着就很乱,影响对业务的理解。

排序可能面对多个参数,比如 SQL 中写 ORDER BY price, quantity。
这个事对 Java 又是个问题。参数个数不同的函数不能混用,这总不能像对付数据类型那样事先把所有参数个数的可能性都穷举一遍。通常的办法就是写个单参数函数,碰到多参数时再临时转换成单参数,比如把这里的 price 和 quantity 拼成一个参数再排序。或者支持集合参数,引用时也得把参数凑成一个集合形式多搞一层。写起来是相当的麻烦。
SQL 没有这样的事,解释型语言可以动态根据数据类型以及个数来决定怎么做。

事还没完,排序还可能针对一个计算式,比如 SQL 中写 ORDER BY price*quantity。这个 price*quantity 并不是在执行这个 SQL 语句之前先计算好的,而是在遍历集合成员才计算的。本质上,price*quantity 是个函数,是一个以当前集合成员为参数的函数,也就是相当于把一个用表达式定义的函数用作了排序运算的参数。
Java 中如果把表达式写到函数的参数中,会在调用前就先计算出来,而不是针对集合成员分别计算。Java 当然允许把一个函数作为参数传递给另一个函数,但写法要麻烦很多,需要事先定义一个函数。

把函数当参数传,又懒得事先定义,这不就是 Lambda 语法吗,Java 现在也支持了啊。
是的,Java 现在有了 Lambda 语法,可以在参数中直接定义匿名函数了。但显然不能写成简单的计算式,编译器无法区别时就会直接给算出来。Lambda 语法仍然是常规函数那一套,要定义参数甚至类型,也有个明显的函数体,只是不起名字而已。而且由于刚才说的数据类型和参数个数问题常常和这个 Lambda 语法搅合到一起,代码更为混乱。
SQL 则把 Lambda 语法化于无形了,甚至都没人把 SQL 这种语法称为 Lambda 语法,但它确实是妥妥地用一个计算式定义了一个函数当参数用。

而且,结构化数据并非简单的单值,而是带有字段的记录。
在 SQL 的表达式参数中引用记录字段时,大多数情况可以直接使用字段名而不必指明字段所在的表,只有在多个同名字段时才需要冠以表名(或表的别名)以示区分。
Java 的 Lambda 语法并不天然知道认得记录,对它来讲就是个参数,取记录的字段(也就是类的成员)要用 dot 操作符,如果表示当前成员的参数名为 it,就要写成 it.price*it.quantity 这种啰嗦的形式。
有了直接引用字段的语法机制后,才可以说是专业面向结构化数据计算的语言。
运算一旦从针对单值变成针对集合,特别是针对结构化数据的集合,麻烦事就多了很多。

说完了吗?并没有。SQL 还支持动态数据结构。
结构化数据计算中,计算结果经常也是有结构的数据,它的结构和运算相关,没办法在代码编写之前就先准备好。所以需要支持动态数据结构的能力。
SQL 中任何一个 SELECT 语句都会产生一个新的数据结构,在代码中可以随意添加删除字段,而不必事先定义结构(类)。Java 这类编译语言又不行,在代码编译前就要把用到的结构(类)都定义好,原则上不能在执行过程中动态产生新的结构。
还有种办法是定义一个复杂的类能够用来描述动态的结构,字段名和值都作为数组成员,然而这已经不是 Java 风格的类了,成员的引用都不能简单地用 dot,而要调用函数,很不方便。除非设计一门新语言,把这种对象作为基础数据类型,才能方便使用。

我们总结一下:集合运算类库,其中参数的类型和数量可以是动态的;化于无形的 Lambda 语法,在其中可以直接引用记录的字段;动态数据结构。
这些我们通称为程序语言的集合化特性!有了集合化特性,才能方便地处理批量的结构化数据。
SQL 中这些看起来理所当然的语法体系,其实背后并没有那么简单,这需要精心的设计。

按这个标准去衡量,Java 本身固然不行,在 Java 上增加再多的类库也不行,基于 Java 设计的新语言 Kotlin 和 Scala 也不满足这标准。其中很关键的化于无形 Lambda 语法,要在解释型语言中才能实现。解释器才知道某个位置的计算式是该先算出值来还是当函数传进去,而编译型语言要写成字符串或者用某种符号体系来区分,这就会破坏代码的简洁性。所以,这些程序语言无论如何不可能像 SQL 一样简洁。
esProc SPL 可以!它是基于 Java 设计的解释型语言,满足上述一切集合化特性。