在学习SQL的时候,我们经常听到这样的说法:SQL是一种声明性语言。你只需要告诉它做什么,不用告诉它怎么做,它就会找到自己的实现方法。也就是说,你只需要用它来描述任务目标,而不需要解释计算过程,这与传统的过程语言有本质的区别。显然,这种编程语言听起来更容易学习和使用。 

真的有那么好吗?

让我们看一个例子。我们使用SQL来查询销售部门的女性员工人数。这是写好的SQL:


SELECT COUNT(*) FROM employee WHERE department='Sales' AND gender='Female'


看起来是这样的:我们不需要关心具体的计算过程(遍历employee表中的每一条记录,符合条件的则count加1,不符合则跳过,最后看count) ),只需说出要查询的目标即可。

我们再看一个例子:按部门查找30岁以上员工的平均工资:


SELECT department,AVERAGE(salary) FROM employee WHERE age>30 GROUP BY department


看起来也不错。在这里,我们真的不必关心如何分组和计算平均值。

虽然SQL仍然是一种语法严格的语言,但我们只有经过一定的学习才能写出正确的语句。不过,如果我们不关心计算过程的话,还是会省下不少功夫的。

让我们看另一个例子。确定贡献销售额前半部分的主要客户。如果我们设计计算过程的话,会是这样的:

  1. 计算所有客户的总销售收入。
  2. 按销售收入倒序对客户进行排序,大客户排在前,小客户排在最后。
  3. 按此顺序从0开始累加每个客户的销售收入,超过总销售收入的一半时停止。那么遍历到的客户就是目标客户。

那么,用 SQL 写出来是什么样的呢?这是我能想到的最简单的方法:


SELECT customer, amount, cum_amount
FROM ( SELECT customer,amount,SUM(amount) OVER (ORDER BY amount DESC) cum_amount FROM ordersummary )
WHERE cum_amount < (SELECT SUM(amount) FROM ordersummary)/2


仔细看看这条SQL语句,它基本上描述了上面的过程。有一个计算总销售额的子查询,后面跟着一个对销售额进行反向排序的子查询。窗口函数用于计算排序列表中每一行的累计销售额。然后主查询过滤掉累计销售额低于总销售额一半的客户。与上述过程唯一的区别是写入顺序。 SQL稍后开始计算总销售收入。还有一点逻辑上的差异,SQL的有序计算和逐步计算不好。在找到排名靠前的产品之前,有必要计算所有累计销售额。

这段SQL语句准确地描述了这样一个过程。描述任务目标而不担心计算过程怎么样?

举个更简单的例子:找到销售收入最高的前10名客户。

部分SQL语句写法如下: 


SELECT TOP 10 customer FROM ordersummary ORDER BY amount DESC


如果使用知名数据库,还需要使用子查询:


SELECT customer
FROM ( SELECT rownumber rn,customer FROM ordersummary ORDER BY amount DESC )
WHERE rn<=10


这两条SQL语句都清楚地告诉了我们计算过程:按照销售收入反向排序后,得到前10名。而且,在这个著名数据库的语法中,需要人为地创建一个序号,这意味着数据库需要更清楚地告诉如何计算。

如果我们看几百行SQL(存储过程),我们可以更清楚地看到,SQL仍然诚实地描述了计算过程,并且不同的计算过程可以带来截然不同的计算性能甚至结果。

事实上,任何编程语言在某种程度上都可以说是声明性语言:即它只需要关心目标而不需要关心过程。

如果用Java写程序,只需要关心变量如何变化,而不用关心CPU中寄存器的动作,但用汇编语言,就需要关心。同样,在使用汇编语言时,你需要关心寄存器的值,但不必担心与非门在CPU中如何运行。

在用SQL编写代码时,一般不需要担心变量和循环的具体动作。它没有变量的概念,但是有表、字段以及相关计算方法的概念,这个层面的流程也需要关注。从这个意义上来说,SQL和其他编程语言只是在描述问题上抽象层次不同,在解释过程上并没有本质区别。

为什么很多人认为SQL是一种所谓的声明式语言?这是因为SQL的设计刻意弱化了“过程性”特征,为了让语句更像英语,将基本操作放入了语句的每个子句中。当计算任务涉及的所有步骤都是SQL抽象层面内的基本操作时,可以将其写成一条语句,看似向SQL描述了任务目标。

然而,传统的编程语言通常不会将多个基本操作设计成一条语句的子句或函数参数,并提倡程序员将它们组合起来。这样,人们就会觉得有必要描述这个过程,而不具有“陈述性”的特征。

然而,语句的子句结构即使很复杂,也是有限的。当任务超出了这个结构的范围,需要使用嵌套子查询或中间结果来描述它时,SQL所谓的“声明式”假象就会暴露出来。还是有必要如实描述一下过程和方法。回顾一下前面的例子,就可以清楚地看出这一点。

在面对一些基本的查询任务时,SQL确实比Java等高级语言更容易学习和使用,但这并不是因为它比Java有更强的“声明性”特性,而是因为它在结构化数据计算方面具有更高的抽象层次比Java。

SQL刻意弱化了“过程性”特征,比如没有中间变量,这使得它描述过程的能力非常弱。当面对复杂的任务时,难度急剧增加,有些任务无法单独使用SQL来实现,因为没有过程化能力的SQL不是图灵完备的。所以,数据库厂商后来不得不补充和发明存储过程和CTE语法。

如果我们发明一种对数据计算具有高度抽象性同时保留其过程性特征的语言,你会发现它比 SQL 更容易学习和使用,尤其是在面对复杂的业务逻辑时。嗯,这就是集算器SPL。


作者:Judy Liu