SQL 行值轮转

SQL 行值轮转,你想返回每个员工的姓名、工资,以及下一个最高和最低的工资值。如果没有找到更高或更低的工资值,你希望结果集可以“折回”(第一个 SAL 的前一行是最后一个 SAL;反之,最后一个 SAL 的下一行即是第一个 SAL)。

SQL 行值轮转 问题描述

你想返回每个员工的姓名、工资,以及下一个最高和最低的工资值。如果没有找到更高或更低的工资值,你希望结果集可以“折回”(第一个 SAL 的前一行是最后一个 SAL;反之,最后一个 SAL 的下一行即是第一个 SAL)。你希望返回如下所示的结果集。

ENAME             SAL    FORWARD     REWIND
---------- ---------- ---------- ----------
SMITH             800        950       5000
JAMES             950       1100        800
ADAMS            1100       1250        950
WARD             1250       1250       1100
MARTIN           1250       1300       1250
MILLER           1300       1500       1250
TURNER           1500       1600       1300
ALLEN            1600       2450       1500
CLARK            2450       2850       1600
BLAKE            2850       2975       2450
JONES            2975       3000       2850
SCOTT            3000       3000       2975
FORD             3000       5000       3000
KING             5000        800       3000

SQL 行值轮转 解决方案

对于 Oracle 用户而言,窗口函数 LEAD OVERLAG OVER 使得本问题解决起来相对容易,而且代码可读性更好。对于其他数据库,可以使用标量子查询,不过 Tie 可能会带来问题。由于存在 Tie 的问题,对于不支持窗口函数的关系数据库管理系统,我们只能提供一个近似的解决方案。
DB2、SQL Server、MySQLPostgreSQL
使用标量子查询为每一个工资值找到它的下一个和前一个的工资值。

 1  select e.ename, e.sal,
 2         coalesce(
 3           (select min(sal) from emp d where d.sal > e.sal),
 4           (select min(sal) from emp)
 5         ) as forward,
 6         coalesce(
 7           (select max(sal) from emp d where d.sal < e.sal),
 8           (select max(sal) from emp)
 9         ) as rewind
10    from emp e
11   order by 2

Oracle
使用窗口函数 LAG OVERLEAD OVER 访问当前行的上一行和下一行记录。

1 select ename,sal,
2        nvl(lead(sal)over(order by sal),min(sal)over()) forward,
3        nvl(lag(sal)over(order by sal),max(sal)over()) rewind
4   from emp

SQL 行值轮转 扩展知识

DB2、SQL Server、MySQLPostgreSQL
标量子查询方案并没有真正解决本问题。它只是一个近似的方案,当两行记录包含相同的 SAL 时,该解决方案就会返回不正确的结果。不过,在没有窗口函数可用的情况下,它已经是最好的方案了。
Oracle
(默认情况下,除非有特别指定。)窗口函数 LAG OVERLEAD OVER 将分别返回当前行的上一行和下一行记录。“上一行”或“下一行”取决于 OVER 子句里的 ORDER BY 部分。如果仔细阅读本解决方案的代码,我们会发现它首先按照 SAL 排序数据集,并提取出了当前行的上一行和下一行。

select ename,sal,
       lead(sal)over(order by sal) forward,
       lag(sal)over(order by sal) rewind
  from emp
 
ENAME             SAL    FORWARD     REWIND
---------- ---------- ---------- ----------
SMITH             800        950
JAMES             950       1100        800
ADAMS            1100       1250        950
WARD             1250       1250       1100
MARTIN           1250       1300       1250
MILLER           1300       1500       1250
TURNER           1500       1600       1300
ALLEN            1600       2450       1500
CLARK            2450       2850       1600
BLAKE            2850       2975       2450
JONES            2975       3000       2850
SCOTT            3000       3000       2975
FORD             3000       5000       3000
KING             5000                  3000

注意,员工 SMITH 的 REWINDNull,而 KING 的 FORWARD 也是 Null;这是因为两个人的 SAL 分别是最低值和最高值。“问题”部分提到,FORWARDREWIND 若出现 Null 值,则应该“折回”。这就意味着,对于最大的 SALFORWARD 值应为 EMP 表中最小的 SAL;而对于最小的 SALREWIND 值应为最大的 SAL。没有指定分区(即 OVER 子句后面跟一对空括号)的窗口函数 MIN OVERMAX OVER 将分别返回最大和最小的 SAL。结果集如下所示。

select ename,sal,
       nvl(lead(sal)over(order by sal),min(sal)over()) forward,
       nvl(lag(sal)over(order by sal),max(sal)over()) rewind
  from emp
 
ENAME             SAL    FORWARD     REWIND
---------- ---------- ---------- ----------
SMITH             800        950       5000
JAMES             950       1100        800
ADAMS            1100       1250        950
WARD             1250       1250       1100
MARTIN           1250       1300       1250
MILLER           1300       1500       1250
TURNER           1500       1600       1300
ALLEN            1600       2450       1500
CLARK            2450       2850       1600
BLAKE            2850       2975       2450
JONES            2975       3000       2850
SCOTT            3000       3000       2975
FORD             3000       5000       3000
KING             5000       800        3000

LAG OVERLEAD OVER 还有一个非常有用的功能,就是可以指定向前或者向后移动多少行。对于本例而言,我们只往前或往后移动了一行。如果你想往前移动 3 行,并且往后移动 5 行,做法非常简单。只需要指定移动值分别为 3 和 5 即可,如下所示。

select ename,sal,
       lead(sal,3)over(order by sal) forward,
       lag(sal,5)over(order by sal) rewind
  from emp
 
ENAME             SAL    FORWARD     REWIND
---------- ---------- ---------- ----------
SMITH             800       1250
JAMES             950       1250
ADAMS            1100       1300
WARD             1250       1500
MARTIN           1250       1600
MILLER           1300       2450        800
TURNER           1500       2850        950
ALLEN            1600       2975       1100
CLARK            2450       3000       1250
BLAKE            2850       3000       1250
JONES            2975       5000       1300
SCOTT            3000                  1500
FORD             3000                  1600
KING             5000                  2450

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程