SQL 查找骑士值

SQL 查找骑士值,你想返回一个结果集,其中包括每个员工的姓名、部门、工资、入职时间以及每一个部门里最近入职的那个员工的工资。你希望返回如下所示的结果集。

SQL 查找骑士值 问题描述

你想返回一个结果集,其中包括每个员工的姓名、部门、工资、入职时间以及每一个部门里最近入职的那个员工的工资。你希望返回如下所示的结果集。

DEPTNO ENAME             SAL HIREDATE    LATEST_SAL
------ ---------- ---------- ----------- ----------
    10 MILLER           1300 23-JAN-1982       1300
    10 KING             5000 17-NOV-1981       1300
    10 CLARK            2450 09-JUN-1981       1300
    20 ADAMS            1100 12-JAN-1983       1100
    20 SCOTT            3000 09-DEC-1982       1100
    20 FORD             3000 03-DEC-1981       1100
    20 JONES            2975 02-APR-1981       1100
    20 SMITH             800 17-DEC-1980       1100
    30 JAMES             950 03-DEC-1981        950
    30 MARTIN           1250 28-SEP-1981        950
    30 TURNER           1500 08-SEP-1981        950
    30 BLAKE            2850 01-MAY-1981        950
    30 WARD             1250 22-FEB-1981        950
    30 ALLEN            1600 20-FEB-1981        950

我把 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

 1 select deptno,
 2        ename,
 3        sal,
 4        hiredate,
 5        max(latest_sal)over(partition by deptno) latest_sal
 6   from (
 7 select deptno,
 8        ename,
 9        sal,
10        hiredate,
11        case
12          when hiredate = max(hiredate)over(partition by deptno)
13          then sal else 0
14        end latest_sal
15   from emp
16        ) x
17  order by 1, 4 desc

MySQLPostgreSQL
使用两层嵌套的标量子查询。首先,找出每个 DEPTO 对应的最近入职的员工的 HIREDATE。然后使用聚合函数 MAX(如果有重复项)找出每个 DEPTNO 对应的最近入职的员工的 SAL

 1  select e.deptno,
 2         e.ename,
 3         e.sal,
 4         e.hiredate,
 5         (select max(d.sal)
 6            from emp d
 7           where d.deptno  = e.deptno
 8             and d.hiredate =
 9                 (select max(f.hiredate)
10                    from emp f
11                   where f.deptno = e.deptno)) as latest_sal
12    from emp e
13   order by 1, 4 desc

Oracle
使用窗口函数 MAX OVER 返回每个 DEPTNO 对应的最高 SAL 值。在 KEEP 子句中使用函数 DENSE_RANKLAST,并按照 HIREDATE 排序,为给定 DEPTNO 对应的最新 HIREDATE 返回最高的 SAL 值。

1  select deptno,
2         ename,
3         sal,
4         hiredate,
5         max(sal)
6           keep(dense_rank last order by hiredate)
7           over(partition by deptno) latest_sal
8    from emp
9   order by 1, 4 desc

SQL 查找骑士值 扩展知识

DB2 和 SQL Server
首先在 CASE 表达式中使用窗口函数 MAX OVER 找出每个 DEPTNO 对应的最近入职的员工。如果员工的 HIREDATE 等于 MAX OVER 的返回值,那么 CASE 表达式就会返回该员工的 SAL 值;否则,返回 0。这一步的结果如下所示。

select deptno,
      ename,
      sal,
      hiredate,
      case
         when hiredate = max(hiredate)over(partition by deptno)
         then sal else 0
      end latest_sal
  from emp
 
DEPTNO ENAME             SAL HIREDATE    LATEST_SAL
------ ---------- ---------- ----------- ----------
    10 CLARK            2450 09-JUN-1981          0
    10 KING             5000 17-NOV-1981          0
    10 MILLER           1300 23-JAN-1982       1300
    20 SMITH             800 17-DEC-1980          0
    20 ADAMS            1100 12-JAN-1983       1100
    20 FORD             3000 03-DEC-1981          0
    20 SCOTT            3000 09-DEC-1982          0
    20 JONES            2975 02-APR-1981          0
    30 ALLEN            1600 20-FEB-1981          0
    30 BLAKE            2850 01-MAY-1981          0
    30 MARTIN           1250 28-SEP-1981          0
    30 JAMES             950 03-DEC-1981        950
    30 TURNER           1500 08-SEP-1981          0
    30 WARD             1250 22-FEB-1981          0

LATEST_SAL 值要么是 0,要么是最近入职的员工的 SAL,因此我们可以把上述查询作为一个内嵌视图,并在此基础上再次使用 MAX OVER,这次我们要为每个 DEPTNO 返回最大的非零 LATEST_SAL

select deptno,
       ename,
       sal,
       hiredate,
       max(latest_sal)over(partition by deptno) latest_sal
  from (
select deptno,
       ename,
       sal,
       hiredate,
       case
           when hiredate = max(hiredate)over(partition by deptno)
           then sal else 0
       end latest_sal
  from emp
       )x
 order by 1, 4 desc
 
DEPTNO ENAME             SAL HIREDATE    LATEST_SAL
------ ---------- ---------- ----------- ----------
    10 MILLER           1300 23-JAN-1982       1300
    10 KING             5000 17-NOV-1981       1300
    10 CLARK            2450 09-JUN-1981       1300
    20 ADAMS            1100 12-JAN-1983       1100
    20 SCOTT            3000 09-DEC-1982       1100
    20 FORD             3000 03-DEC-1981       1100
    20 JONES            2975 02-APR-1981       1100
    20 SMITH             800 17-DEC-1980       1100
    30 JAMES             950 03-DEC-1981        950
    30 MARTIN           1250 28-SEP-1981        950
    30 TURNER           1500 08-SEP-1981        950
    30 BLAKE            2850 01-MAY-1981        950
    30 WARD             1250 22-FEB-1981        950
    30 ALLEN            1600 20-FEB-1981        950

MySQLPostgreSQL
首先,使用标量子查询找出每个 DEPTNO 对应的最新入职的员工的 HIREDATE

select e.deptno,
       e.ename,
       e.sal,
       e.hiredate,
       (select max(f.hiredate)
          from emp f
         where f.deptno = e.deptno) as last_hire
  from emp e
 order by 1, 4 desc
 
DEPTNO ENAME             SAL HIREDATE      LAST_HIRE
------ ---------- ---------- ----------- -----------
    10 MILLER           1300 23-JAN-1982 23-JAN-1982
    10 KING             5000 17-NOV-1981 23-JAN-1982
    10 CLARK            2450 09-JUN-1981 23-JAN-1982
    20 ADAMS            1100 12-JAN-1983 12-JAN-1983
    20 SCOTT            3000 09-DEC-1982 12-JAN-1983
    20 FORD             3000 03-DEC-1981 12-JAN-1983
    20 JONES            2975 02-APR-1981 12-JAN-1983
    20 SMITH             800 17-DEC-1980 12-JAN-1983
    30 JAMES             950 03-DEC-1981 03-DEC-1981
    30 MARTIN           1250 28-SEP-1981 03-DEC-1981
    30 TURNER           1500 08-SEP-1981 03-DEC-1981
    30 BLAKE            2850 01-MAY-1981 03-DEC-1981
    30 WARD             1250 22-FEB-1981 03-DEC-1981
    30 ALLEN            1600 20-FEB-1981 03-DEC-1981

然后,找出每个 DEPTNO 对应的入职日期等于 LAST_HIRE 的员工的 SAL。使用聚合函数 MAX 找出最大值(如果同一天入职的员工不止一人的话)。

select e.deptno,
       e.ename,
       e.sal,
       e.hiredate,
       (select max(d.sal)
          from emp d
         where d.deptno   = e.deptno
           and d.hiredate =
               (select max(f.hiredate)
                  from emp f
                 where f.deptno = e.deptno)) as latest_sal
  from emp e
 order by 1, 4 desc
 
DEPTNO ENAME             SAL HIREDATE    LATEST_SAL
------ ---------- ---------- ----------- ----------
    10 MILLER           1300 23-JAN-1982       1300
    10 KING             5000 17-NOV-1981       1300
    10 CLARK            2450 09-JUN-1981       1300
    20 ADAMS            1100 12-JAN-1983       1100
    20 SCOTT            3000 09-DEC-1982       1100
    20 FORD             3000 03-DEC-1981       1100
    20 JONES            2975 02-APR-1981       1100
    20 SMITH             800 17-DEC-1980       1100
    30 JAMES             950 03-DEC-1981        950
    30 MARTIN           1250 28-SEP-1981        950
    30 TURNER           1500 08-SEP-1981        950
    30 BLAKE            2850 01-MAY-1981        950
    30 WARD             1250 22-FEB-1981        950
    30 ALLEN            1600 20-FEB-1981        950

Oracle
Oracle 8i 用户不妨采用前面的 DB2 解决方案。Oracle 9i 及后续版本则可以使用下面给出的解决方案。如下 Oracle 解决方案的关键在于利用 KEEP 子句。KEEP 子句为分组或者分区之后的行数据进行排序,并取出每组的第一行或最后一行。试想一下,去掉了 KEEP 子句的解决方案会如何。

select deptno,
       ename,
       sal,
       hiredate,
       max(sal) over(partition by deptno) latest_sal
  from emp
 order by 1, 4 desc
 
DEPTNO ENAME             SAL HIREDATE    LATEST_SAL
------ ---------- ---------- ----------- ----------
    10 MILLER           1300 23-JAN-1982       5000
    10 KING             5000 17-NOV-1981       5000
    10 CLARK            2450 09-JUN-1981       5000
    20 ADAMS            1100 12-JAN-1983       3000
    20 SCOTT            3000 09-DEC-1982       3000
    20 FORD             3000 03-DEC-1981       3000
    20 JONES            2975 02-APR-1981       3000
    20 SMITH             800 17-DEC-1980       3000
    30 JAMES             950 03-DEC-1981       2850
    30 MARTIN           1250 28-SEP-1981       2850
    30 TURNER           1500 08-SEP-1981       2850
    30 BLAKE            2850 01-MAY-1981       2850
    30 WARD             1250 22-FEB-1981       2850
    30 ALLEN            1600 20-FEB-1981       2850

去掉了 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 值。比较下面的两个查询,它们都返回相同的结果集。

select deptno,
       ename,
       sal,
       hiredate,
       max(sal)
         keep(dense_rank last order by hiredate)
         over(partition by deptno) latest_sal
  from emp
 order by 1, 4 desc
 
DEPTNO ENAME             SAL HIREDATE    LATEST_SAL
------ ---------- ---------- ----------- ----------
    10 MILLER           1300 23-JAN-1982       1300
    10 KING             5000 17-NOV-1981       1300
    10 CLARK            2450 09-JUN-1981       1300
    20 ADAMS            1100 12-JAN-1983       1100
    20 SCOTT            3000 09-DEC-1982       1100
    20 FORD             3000 03-DEC-1981       1100
    20 JONES            2975 02-APR-1981       1100
    20 SMITH             800 17-DEC-1980       1100
    30 JAMES             950 03-DEC-1981        950
    30 MARTIN           1250 28-SEP-1981        950
    30 TURNER           1500 08-SEP-1981        950
    30 BLAKE            2850 01-MAY-1981        950
    30 WARD             1250 22-FEB-1981        950
    30 ALLEN            1600 20-FEB-1981        950
 
 
select deptno,
       ename,
       sal,
       hiredate,
       max(sal)
         keep(dense_rank first order by hiredate desc)
         over(partition by deptno) latest_sal
  from emp
 order by 1, 4 desc
 
DEPTNO ENAME             SAL HIREDATE    LATEST_SAL
------ ---------- ---------- ----------- ----------
    10 MILLER           1300 23-JAN-1982       1300
    10 KING             5000 17-NOV-1981       1300
    10 CLARK            2450 09-JUN-1981       1300
    20 ADAMS            1100 12-JAN-1983       1100
    20 SCOTT            3000 09-DEC-1982       1100
    20 FORD             3000 03-DEC-1981       1100
    20 JONES            2975 02-APR-1981       1100
    20 SMITH             800 17-DEC-1980       1100
    30 JAMES             950 03-DEC-1981        950
    30 MARTIN           1250 28-SEP-1981        950
    30 TURNER           1500 08-SEP-1981        950
    30 BLAKE            2850 01-MAY-1981        950
    30 WARD             1250 22-FEB-1981        950
    30 ALLEN            1600 20-FEB-1981        950

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程