SQL 变换已排名的结果集,你想为一个表里的记录排名,然后把它变换成一个 3 列的结果集。这 3 列分别是前 3 名,接下来的 3 个名次,然后是其余各行记录。例如,你希望根据 SAL
值为 EMP
表的员工排名,并把结果变换成一个 3 列的结果集。
SQL 变换已排名的结果集 问题描述
你想为一个表里的记录排名,然后把它变换成一个 3 列的结果集。这 3 列分别是前 3 名,接下来的 3 个名次,然后是其余各行记录。例如,你希望根据 SAL
值为 EMP
表的员工排名,并把结果变换成一个 3 列的结果集。你期望的结果集如下所示。
SQL 变换已排名的结果集 解决方案
本解决方案的关键是先借助窗口函数 DENSE_RANK OVER
根据 SAL
为员工排名,因为该函数允许 Tie 的存在。有了 DENSE_RANK OVER
函数,我们很容易查看前 3 名的工资,接下来的 3 个次高的工资,以及其余工资。
然后,使用窗口函数 ROW_NUMBER OVER
在每个分组内部(前 3 名、接下来的 3 个名次,或其余各行记录)为员工排序。然后,只要做一次常规的行列变换操作,并借助数据库内置的字符串函数对查询结果做出适当格式化即可。下面的解决方案使用 Oracle 语法。DB2 和 SQL Server 2005 都支持窗口函数,相信你能够适当地修改本解决方案以适用于其他数据库。
SQL 变换已排名的结果集 扩展知识
本实例充分展示了窗口函数的威力。上述解决方案看起来很复杂,但是如果我们把它拆解开来一一细看的话,就会发现其实并不难理解。我们先从内嵌视图 X
开始。
如上所示,内嵌视图 X
根据 SAL
为员工排序,同时又允许 Tie 的存在。(因为本解决方案使用的是 DENSE_RANK
函数而不是 RANK
函数,DENSE_RANK
不仅允许 Tie 的存在,还能保证名次连续,中间不留空白。)下一步是从内嵌视图 X
里提取出各行数据,并借助 CASE
表达式评估 DENSE_RANK
返回的排名结果从而实现分组。除此之外,还要调用窗口函数 ROW_NUMBER OVER
根据 SAL
对员工进行组内编号(在前面由 CASE
表达式实现的分组里)。所有这些都由内嵌视图 Y
实现的,如下所示。
查询语句越来越接近最终的形态了,并且如果你是从开始部分(从内嵌视图 X
)一直读到这里的话,一定会发现其实该查询并不是那么难以理解。上述查询返回了每个员工的 SAL
、RNK
(代表该员工的 SAL
在全体员工中的排名)、GRP
(根据每个员工的 SAL
产生的分组),以及 GRP_RANK
(在每一个分组内基于 SAL
生成的编号)。
现在,我们要执行一次传统的行列翻转,同时使用 Oracle 的字符串连接操作符||
把 SAL
附加在 ENAME
的后面。函数 RPAD
确保圆括号里的数值能够上下对齐。最后,针对 GRP_RNK
执行 GROUP BY
操作以确保结果集能包含所有员工。最终的结果集如下所示。
仔细观察以上每一步的查询语句,我们会发现直接读取 EMP
表的操作其实只发生了一次。窗口函数最为引人注目的功能之一就是,只需访问一次原始数据就可以完成很多复杂的任务。不需要自连接或临时表,只要准备好必要的基础数据集,剩下的工作交给窗口函数处理就行了。我们只需要在内嵌视图 X
中访问一次 EMP
表。此后,只要多次变换该查询结果直到得出我们希望看到的结果集即可。想想我们创建出了这么复杂的报表,却只读取过一次原始数据,真是非常酷。