SQL 查找骑士值,你想返回一个结果集,其中包括每个员工的姓名、部门、工资、入职时间以及每一个部门里最近入职的那个员工的工资。你希望返回如下所示的结果集。
SQL 查找骑士值 问题描述
你想返回一个结果集,其中包括每个员工的姓名、部门、工资、入职时间以及每一个部门里最近入职的那个员工的工资。你希望返回如下所示的结果集。
我把 LATEST_SAL
值称作“骑士值”(knight value),因为找出它们的方法类似于国际象棋游戏中骑士的路径。我们找到结果的方式就像一个骑士确定新位置:跳到一行,然后转向并跳到一个不同的列(图 11-1)。为找到正确的 LATEST_SAL
值,你必须先定位(跳到)每个 DEPTNO
对应的最新 HIREDATE
,然后选择(跳到)那一行的 SAL
列。
图 11-1:骑士值来源于国际象棋中骑士的走法
“骑士值”是由我的一个非常聪明的同事 Kay Young 创造出来的。在他帮我做完正确性检查后,我告诉他我被难住了,因为无法为本实例想出一个恰到好处的标题。考虑到需要先对当前行数据做出评估,然后“跳”到另外一行并取出某列的值,于是他提出了“骑士值”这个名词。
SQL 查找骑士值 解决方案
DB2 和 SQL Server
在子查询中使用 CASE
表达式,并返回每个 DEPTNO
对应的最近入职的那个员工的 SAL
;对于其他工资值,则返回 0。在外层的查询中使用窗口函数 MAX OVER
为每个员工的部门返回非零的 SAL
。
MySQL 和 PostgreSQL
使用两层嵌套的标量子查询。首先,找出每个 DEPTO
对应的最近入职的员工的 HIREDATE
。然后使用聚合函数 MAX
(如果有重复项)找出每个 DEPTNO
对应的最近入职的员工的 SAL
。
Oracle
使用窗口函数 MAX OVER
返回每个 DEPTNO
对应的最高 SAL
值。在 KEEP
子句中使用函数 DENSE_RANK
和 LAST
,并按照 HIREDATE
排序,为给定 DEPTNO
对应的最新 HIREDATE
返回最高的 SAL
值。
SQL 查找骑士值 扩展知识
DB2 和 SQL Server
首先在 CASE
表达式中使用窗口函数 MAX OVER
找出每个 DEPTNO
对应的最近入职的员工。如果员工的 HIREDATE
等于 MAX OVER
的返回值,那么 CASE
表达式就会返回该员工的 SAL
值;否则,返回 0。这一步的结果如下所示。
LATEST_SAL
值要么是 0,要么是最近入职的员工的 SAL
,因此我们可以把上述查询作为一个内嵌视图,并在此基础上再次使用 MAX OVER
,这次我们要为每个 DEPTNO
返回最大的非零 LATEST_SAL
。
MySQL 和 PostgreSQL
首先,使用标量子查询找出每个 DEPTNO
对应的最新入职的员工的 HIREDATE
。
然后,找出每个 DEPTNO
对应的入职日期等于 LAST_HIRE
的员工的 SAL
。使用聚合函数 MAX
找出最大值(如果同一天入职的员工不止一人的话)。
Oracle
Oracle 8i 用户不妨采用前面的 DB2 解决方案。Oracle 9i 及后续版本则可以使用下面给出的解决方案。如下 Oracle 解决方案的关键在于利用 KEEP
子句。KEEP
子句为分组或者分区之后的行数据进行排序,并取出每组的第一行或最后一行。试想一下,去掉了 KEEP
子句的解决方案会如何。
去掉了 KEEP
子句的 MAX OVER
只会简单地返回每个 DEPTNO
对应的最高工资值,而不是最近入职的那个员工的 SAL
。这里的 KEEP
子句通过指定 ORDER BY HIREDATE
按照 HIREDATE
为每个 DEPTNO
对应的工资值进行排序。然后,DENSE_RANK
函数按照升序为 HIREDATE
排序。最后,函数 LAST
决定针对哪一行记录使用聚合函数:基于 DENSE_RANK
排序最后的一行。对本例而言,聚合函数 MAX
针对的是最后一行 HIREDATE
所对应的 SAL
列。其实就是为了保留每个 DEPTNO
对应的 HIREDATE
排序最后的那个 SAL
值。
我们基于某一列(HIREDATE
)为每个 DEPTNO
对应的行做排序,却针对另一列(SAL
)做聚合计算(MAX
)。Oracle 具备的这种先针对某一个维度做排序,继而又针对另一个维度做聚合计算的能力非常有用,其他数据库需要额外的连接查询和内嵌视图才能达到同样的效果。最后,通过在 KEEP
子句后面跟一个 OVER
子句,我们就能返回由 KEEP
子句为每一行“保留”下来的 SAL
值。
另外,我们还可以对 HIREDATE
实行降序排列,并保留第一个 SAL
值。比较下面的两个查询,它们都返回相同的结果集。