1. 将数据按产品分组,将每组排序,取出前 10 名;
2. 将所有的前 10 名取交集;
由于我们事先不知道会有多个产品,这样需要把分组结果也存储在一个临时表中,而这个表有个字段要存储对应的分组成员,这是 SQL 不支持的,办法就行不通了。
如果有窗口函数的支持,可以转换思路,按产品分组后,计算每个销售员在所有分组的前 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 有集合概念,但并未把集合作为一种基础数据类型提供,这使得大量集合运算在思维和书写时都需要绕路。
我们在上面的计算中使用了关键字 top,事实上关系代数理论中没有这个东西(它可以被别的计算组合出来),这不是 SQL 的标准写法。
我们来看一下没有 top 时找前 10 名会有多困难?
大体思路是这样:找出比自己大的成员个数作为是名次,然后取出名次不超过 10 的成员,写出的 SQL 如下:
select sales
from ( select A.sales sales, A.product product,
(select count(*)+1 from sales_amount
where A.product=product AND A.amount<=amount) ranking
from sales_amount A )
where product='AC' AND ranking<=10
或
select sales
from ( select A.sales sales, A.product product, count(*)+1 ranking
from sales_amount A, sales_amount B
where A.sales=B.sales and A.product=B.product AND A.amount<=B.amount
group by A.sales,A.product )
where product='AC' AND ranking<=10
这样的 SQL 语句,专业程序员写出来也未必容易吧!而仅仅是计算了一个前 10 名。
退一步讲,即使有 top,那也只是使取出前一部分轻松了。如果我们把问题改成取第 6 至 10 名,或者找比下一名销售额超过 10% 的销售员,困难仍然存在。
造成这个现象的原因就是 SQL 的第三个重要缺点:缺乏有序支持。
SQL 继承了数学上的无序集合,这直接导致与次序有关的计算相当困难,而可想而知,与次序有关的计算会有多么普遍(诸如比上月、比去年同期、前 20%、排名等)。
SQL2003 标准中增加的窗口函数提供了一些与次序有关的计算能力,这使得上述某些问题可以有较简单的解法,在一定程度上缓解 SQL 的这个问题。但窗口函数的使用经常伴随着子查询,而不能让用户直接使用次序访问集合成员,还是会有许多有序运算难以解决。
我们现在想关注一下上面计算出来的“好”销售员的性别比例,即男女各有多少。一般情况下,销售员的性别信息会记在花名册上而不是业绩表上,简化如下:
employee | 员工表 |
name | 员工姓名,假定无重名 | </