再来谈离散性,Java 比 SQL 又有什么优势?

我们讨论了 SQL 对 Java 的优势,也就是集合化特性,我们现在再来看看 Java 比 SQL 有什么优势。
Java 的代码长是长了,看起来也乱,但仔细研读会发现,它描述的运算逻辑并不困难,基本上就是按部就班地实现业务目标。也就是说,Java 是书写繁琐,而不是思考困难。
但 SQL 却不一样,看懂每一个子查询的技术意义并不难,但你却很难明白它到底想干吗,是怎样为最终的业务目标服务的。也就是说,SQL 写起来要简洁一些,但思维难度却更大了。
这是为什么?
我们之前讲过一期 三行五行的 SQL 只存在于教科书和培训班 ,指出 SQL 有集合化不彻底、缺乏有序支持等问题,这些问题,以及 SQL 还有的其它问题,都有一个共同的根源,这导致虽然 SQL 的繁琐度低于 Java,但难度却更大。

我们还是从例子出发,来计算一个数列的中位数。
这里就不用结构化数据了,只用个简单数组,否则会打到 Java 的软肋上,Java 代码过长时就把关键问题掩盖了。

SQL:
    WITH TN AS (SELECT ROWNUMBER() OVER (ORDER BY V) RN,V FROM T),
         N AS (SELECT COUNT(*) N FROM T)
    SELECT AVERAGE(V) FROM TN,N
    WHERE RN>=N.N/2 AND RN<=N.N/2+1

Java:
    Array.sort(v);
    return (v[(v.length-1)/2] + v[v.length/2])/2;

不涉及结构化数据及 Lambda 语法时,Java 常常就会显得比 SQL 简洁了。而且仔细理解这两段代码的计算过程还能发现,Java 代码不仅简洁,效率也更好。
表面上看,SQL 的困难在于是不能直接用序号取出成员,它就没有序号的概念,要硬造个序号列,这里还用了窗口函数,否则序列还很难造。而 Java 则可以方便地用序号从数组取出成员来计算。

这里的根源在于 Java 和 SQL 中数据模型的不同。
Java 等高级程序语言中的数据都是以一些不可以再拆分的原子数据为基础的,比如数、串等。原子数据可以构成集合和记录等较复杂的数据,集合和记录也是某种数据,也可以再组合成更大的集合和记录。而构成集合和记录的数据并不依附于集合和记录,可以独立存在和参与计算,自然会提供从集合和记录中拆解出成员的操作(用序号和字段取成员)。这种自由的数据组织形式,我们称为离散性。支持了离散性后,可以轻易地构造出集合的集合、以及字段取值是记录的记录。
SQL 则把表(也就是记录的集合)作为一种原子数据,它并不是由更基础的原子数据组合成的。SQL 的数据也没有可组合性,集合的集合和记录的记录在 SQL 中是不存在的。SQL 中记录必须依附于表,不能独立存在,也就是成员必须依附于集合,拆解集合成员的操作是没有意义的,因为拆出来也没有对应的数据类型来承载。所谓拆解,其实是用 WHERE 这种过滤运算,这就会显得有点绕。过滤运算本质上是在计算子集,结果仍然是表(集合),单条记录实质上是只有一条记录的表(只有一个成员的集合)。SQL 这种数据组织方式很不自由,缺失了离散性。
几乎所有高级语言都天然支持离散性,然而 SQL 没有。

离散性是个很自然的特性,事物本来也是从简单到复杂发展的,这符合人们的自然思维。业务逻辑并不完全是针对集合整体的,还有很多针对具体集合成员或集合外游离数据的操作。缺失离散性,会加大这类不是整体集合相关运算的难度,也就是出现“绕”。
举个简单的结构化数据的例子:比如要列出年龄和收入都大于张三的员工,按自然思路想当然地写出 SQL 会是这样:

WITH A AS (SELECT * FROM employee WHERE name=...)
SELECT * FROM employee WHERE age>A.age AND salary>A.salary

但可惜这个 SQL 是非法的,它的后半截要用 JOIN 来写:

SELECT T.* FROM employee T,A WHERE T.age>A.age AND T.salary>A.salary

有点绕吧。这可以用来理解 SQL 中表和记录的差异,它没有一种数据类型可以承载记录。
Java 对结构化数据这些集合运算支持不好,我们改用集合化特性更好的 SPL 来完成同样的运算:

a = employee.select@1(name==...)
employee.select( age>a.age && salary>a.salary )

这就是自然思维了。

集合化是语法形式,对应代码的繁度;离散性是数据模型,对应代码的难度;缺失集合化的 Java 写出来的代码很繁,缺失离散性的 SQL 写出来的代码倒不见得很长,但是会很绕,难度变大。
SQL 难度大的问题几乎都是缺失离散性造成的。没有集合的集合,SQL 在分组时无法保持分组子集,必须强迫聚合,SQL 的集合化不彻底。没有游离记录及其构成的集合,只能用外键表示数据之间的关联关系,代码繁琐又难懂,运算性能还差,缺乏离散性的SQL 无法采用直观的引用机制描述关联。特别地,没有离散性的支持,SQL 很难描述有序计算,有序计算是典型的离散和集合的结合物,成员的次序在集合中才有意义,这要求集合,有序计算时又要将每个成员与相邻成员区分开,会强调离散。
限于篇幅,这一期就不详细展开讲这些问题,以后会专门一个个再讲。
我们需要集合化和离散性相结合的语言,同时具有 SQL 和 Java 的优点。嗯,这也就是为什么要发明集算器 SPL,也是为什么 SPL 的理论体系叫作离散数据集。