SQL还有多少"理所当然";还有那些"就是这样"

前言废话——sql是程序员的饭碗,繁琐but万能,但能干并不意味着适合干,每当多表关联寻找外键时,我都在经历一种没有选择的痛苦。sql不完美,但长期代码让人无暇顾及完美,再痛苦的呐喊到最后都归于疲倦已极的无奈,就在还愿意记下它们的时候存个档吧。午饭吃饱了,打个盹,做个梦去。

正文:

发明SQL的主要目的是为结构化数据提供一种屏弊数据物理存储方案的访问方法,因此SQL中大量使用了类英语的词汇和语法以降低理解和书写困难。作为SQL基础理论的关系代数是个完备的计算体系,原则上可以计算一切。这样看来,我们理所应当地用SQL完成各种数据计算需求。

但是,尽管关系数据库取得了巨大的成功,SQL却显然没有达到其发明初衷,除了极少数简单的查询可由终端用户采用SQL完成外,绝大多数的SQL使用者仍是技术人员,甚至许多复杂的查询对技术人员也不是件容易的事。

 

通过一个很简单的例子来考察SQL在计算方面的缺点。

假设有一个由三个字段构成的销售业绩表sales_amount(为了简化问题,省去日期信息):

Sales         销售员姓名,假定无重名

Product    销售的产品

Amount    该销售员在该产品上的销售额

        

现在我们想知道空调和电视销售额都在前10名的销售员名单。这个问题很简单,人们会很自然地设计出如下计算过程:a、先按空调销售额排序,找出前10名;b、再按电视销售额排序,找出前10名;c、对a、b的结果取交集。

 

用SQL做:

a、找出空调销售额前10名,这很简单:

select top 10 sales from sales_amount where product='AC' order by amount desc

b、找出电视销售额前10名,动作一样:

select top 10 sales from sales_amount where product='TV' order by amount desc

c、求1、2的交集。这有点麻烦,因为SQL不支持步骤化、上两步的计算结果无法保存,所以只能重抄一遍:

select * from

( select top 10 sales from sales_amount where product='AC' order by amount desc )

intersect

( select top 10 sales from sales_amount where product='TV' order by amount desc )

一个只3步的简单计算得用SQL写成这样,而日常计算多达10几步的比比皆是,这显然超出来许多人的可接受能力。

这样,我们知道SQL的第一个重要缺点:不支持步骤化。把复杂的计算分步可以在很大程度地降低问题的难度,反过来,把多步计算汇成一步则很大程度地提高了问题的难度。

如果老师要求小学生做应用题时只能列一个算式完成,小朋友们会多么苦恼(当然,不乏一些聪明孩子搞得定)。

 

SQL查询不能分步,但用SQL写出的存储过程可以分步,那么用存储过程是否可以方便地解决这个问题呢?

不提使用存储过程的技术环境有多复杂(这足以令大多数人却步了)和数据库的差异性造成的不兼容,我们只从理论上来看用分步SQL是否能让这个计算更简捷些。

a、计算空调销售额前10名。语句还是那样,但我们需要把结果存起来供第3步用,而SQL中只能用表存储集合数据,这样我们要建一个临时表:

create temporary table x1 as

select top 10 sales from sales_amount where product='AC' order by amount desc

b、计算电视销售额前10名。类似地:

create temporary table x2 as

select top 10 sales from sales_amount where product='TV' order by amount desc

c、求交集,前面麻烦了,这步就简单些:

select * from x1 intersect x2

分步后思路变清晰了,但临时表的使用仍显繁琐。在以批量结构化数据计算中,作为中间结果的临时集合是相当普遍的,如果都建立临时表来存储,不仅运算效率低,同时也不直观。

 

而且,SQL不允许某个字段取值是集合(即临时表),这样,有些计算即使容忍了繁琐也做不到。

如果我们把问题改为计算所有产品销售额都在前10名的销售员,试想一下应当如何计算,继续延用上述的思路很容易想到:

1.  将数据按产品分组,将每组排序,取出前10名;

2.  将所有的前10名取交集;

由于我们事先不知道会有多个产品,这样需要把分组结果也存储在一个临时表中,而这个表有个字段要存储对应的分组成员,这是SQL不支持的,办法就行不通了。

如果有窗口函数(SQL2003标准)的支持,可以转换思路,按产品分组后,计算每个销售员在所有分组的前10名中出现的次数,若与产品总数相同,则表示该销售员在所有产品销售额中均前在前10名内。

select sales

from ( select sales,

           from ( select sales,

                                     rank() over (partition by product order by amount desc ) ranking

                            from sales_amount)

           where ranking <=10 )

group by sales

having count(*)=(select count(distinct product) from sales_amount)

这样的SQL,有多少人会写呢?

况且,窗口函数在许多数据库中还不支持。那么,就只能用存储过程写循环依次计算每个产品的前10名,与上一次结果做交集。这个过程比用高级语言编写程序并不简单多少,而且仍然要面向临时表的繁琐。

现在,我们知道了SQL的第二个重要缺点:集合化不彻底。虽然SQL有集合概念,但并未把集合作为一种基础数据类型提供,这使得大量集合运算在思维和书写时都需要转换翻译。

posted @ 2013-11-01 13:37  许杰的博客  阅读(241)  评论(0编辑  收藏  举报