Over开窗语句

开窗语句

开窗函数支持分区、排序和框架三种元素,其语法格式如下:
  OVER (
         [  ]
         [  ]
         [  ]
        )

窗口分区:

就是将窗口指定列具有相同值的那些行进行分区,分区与分组比较类似,但是分组指定后对于整个SELECT语句只能按照这个分组,不过
分区可以在一条语句中指定不同的分区。

   ::=
  PARTITION BY value_expression , ... [ n ]

窗口排序:

分区之后可以指定排序列,那么在窗口计算之前,各个窗口的行的逻辑顺序将确定。

   ::=
  ORDER BY order_by_expression
      [ COLLATE collation_name ]
      [ ASC | DESC ]
      [ ,...n ]

窗口框架:

框架是对窗口进行进一步的分区,框架有两种范围限定方式:一种是使用Rows子句,通过指定当前行之前或之后的固定数目的行来限制分区中的行数;
另一种是Range子句,按照排序列的当前值,根据相同值来确定分区中的行数。

示例01: 创建Test表(fName,fClass,fScore)

DROP TABLE Test;
WITH DA (fName,fClass,fScore,fClassName) AS (
  SELECT 'XS001', 12, 74, '计算机系2020级2班' UNION
  SELECT 'XS002', 11, 95, '计算机系2020级1班' UNION
  SELECT 'XS003', 11, 95, '计算机系2020级1班' UNION
  SELECT 'XS004', 11, 80, Null                UNION
  SELECT 'XS005', 12, 92, Null                UNION
  SELECT 'XS006', 13, 99, '计算机系2020级3班' UNION
  SELECT 'XS007', 13, 99, '计算机系2020级3班' UNION
  SELECT 'XS008', 13, 45, Null                UNION
  SELECT 'XS009', 13, 55, '计算机系2020级3班' UNION
  SELECT 'XS010', 13, 78, '计算机系2020级3班'
)
SELECT * INTO Test FROM DA;

示例02: Range,Rows的用法

Select fName,fClass,fScore,
  Sum(fScore) Over(Order By fScore) C1,
  Sum(fScore) Over(Order By fScore Range Between unbounded Preceding AND Current Row) C2,
  Sum(fScore) Over(Order By fScore Rows  Between unbounded Preceding AND Current Row) C3,
  Sum(fScore) Over(Order By fScore Rows  Between 1 Preceding AND 2 Following) C4,
  Sum(fScore) Over(Order By fScore Rows  Between 1 Preceding AND Current Row) C5,
  Sum(fScore) Over(Order By fScore Rows  unbounded Preceding) C6
From Test

-- 运行结果
fName fClass  fScore  C1  C2  C3  C4  C5  C6
---------------------------------------------
XS008   13      45    45  45  45  174 45  45
XS009   13      55    100 100 100 252 100 100
XS001   12      74    174 174 174 287 129 174
XS010   13      78    252 252 252 324 152 252
XS004   11      80    332 332 332 345 158 332
XS005   12      92    424 424 424 362 172 424
XS002   11      95    614 614 519 381 187 519
XS003   11      95    614 614 614 388 190 614
XS006   13      99    812 812 713 293 194 713
XS007   13      99    812 812 812 198 198 812
---------------------------------------------

结果分析:
Range 是逻辑窗口,是指定当前行对应值的范围取值,列数不固定,只要行值在范围内,对应列都包含在内。
Rows 是物理窗口,即根据order by 子句排序后,取的前N行及后N行的数据计算。(与当前行的值无关,只与排序后的行号相关)

【C1】未指定窗口,所以默认为 Range unbounded Preceding AND Current Row,故此与【C2】值相同。
【C2】Range BETWEEN unbounded preceding AND Current Row 表示指定取值范围为:当前名次与之前所有名次的值。
【C3】Rows BETWEEN unbounded preceding AND Current Row 表示指定取值范围为:当前行与当前行前面的所有行的值。
【C4】Rows BETWEEN 1 preceding AND 2 following 表示指定取值范围为:当前行与前一行和后两行的值。
【C5】Rows BETWEEN 1 preceding AND Current Row 表示指定取值范围为:当前行与前一行的值。
【C6】Rows unbounded Preceding 表示指定取值范围为:当前行与前一行的值,默认到当前行.

示例03: 分组排序

-- 查询各个班级的第一名:分组排名
WITH PM (fName,fClass,fScore,fRanking) AS
(
  select fName,fClass,fScore,rank() over(partition by fClass order by fScore desc)
  from Test
)
SELECT * FROM PM WHERE fRanking=1;
-- 运行结果
-----------------------------------
fName fClass  fScore  fRanking
XS002   11      95      1
XS003   11      95      1
XS005   12      92      1
XS006   13      99      1
XS007   13      99      1
-----------------------------------

示例04: 跳跃排序和连续排序

Rank()和Dense_Rank()可以将所有的都查找出来:
如上可以看到采用Rank可以将并列第一名的都查找出来;

Rank()和Dense_rank()区别:Rank()是跳跃排序,有两个第二名时接下来就是第四名;


-- 跳跃排序
WITH PM (fName,fClass,fScore,fRanking) AS
(
  select fName,fClass,fScore,rank() over(partition by fClass order by fScore desc)
  from Test
)
SELECT * FROM PM;

-- 运行结果
fName fClass  fScore  fRanking
-----------------------------------
XS002   11      95      1
XS003   11      95      1
XS004   11      80      3  --直接就跳到了第3名
XS005   12      92      1
XS001   12      74      2
XS006   13      99      1
XS007   13      99      1
XS010   13      78      3
XS009   13      55      4
XS008   13      45      5
-----------------------------------

-- 连续排序
WITH PM (fName,fClass,fScore,fRanking) AS
(
  select fName,fClass,fScore,Dense_rank() over(partition by fClass order by fScore desc)
  from Test
)
SELECT * FROM PM;

-- 运行结果
fName fClass  fScore  fRanking
-----------------------------------
XS002   11      95      1
XS003   11      95      1
XS004   11      80      2  --连续排序,无跳跃
XS005   12      92      1
XS001   12      74      2
XS006   13      99      1
XS007   13      99      1
XS010   13      78      2
XS009   13      55      3
XS008   13      45      4
-----------------------------------

示例05: 求和

WITH PM (fName,fClass,fScore,fSum) AS
(
  select fName,fClass,fScore,SUM(fScore) Over(Partition By fClass Order By fScore Desc)
  from Test
)
SELECT * FROM PM;
-- 运行结果
fName fClass  fScore  fSum
-----------------------------------
XS002   11      95     190
XS003   11      95     190
XS004   11      80     270   --这一行是按fClass分组的求和
XS005   12      92     92
XS001   12      74     166   --这一行是按fClass分组的求和
XS006   13      99     198
XS007   13      99     198
XS010   13      78     276
XS009   13      55     331
XS008   13      45     376   --这一行是按fClass分组的求和
-----------------------------------

-- 换以下方式,增加 Rows BETWEEN unbounded Preceding AND unbounded Following 限定
WITH PM (fName,fClass,fScore,fSum) AS
(
  Select fName,fClass,fScore,
  SUM(fScore) Over(Partition By fClass Order By fScore Desc Rows BETWEEN unbounded Preceding AND unbounded Following)
  from Test
)
SELECT * FROM PM;
-- 运行结果
fName fClass  fScore  fSum
-----------------------------------
XS002   11      95     270   --所有行都是按fClass分组的求和
XS003   11      95     270   --所有行都是按fClass分组的求和
XS004   11      80     270   --所有行都是按fClass分组的求和
XS005   12      92     166   --所有行都是按fClass分组的求和
XS001   12      74     166   --所有行都是按fClass分组的求和
XS006   13      99     376   --所有行都是按fClass分组的求和
XS007   13      99     376   --所有行都是按fClass分组的求和
XS008   13      45     376   --所有行都是按fClass分组的求和
XS009   13      55     376   --所有行都是按fClass分组的求和
XS010   13      78     376   --所有行都是按fClass分组的求和
-----------------------------------

示例06: 求最小值和最大值

–First_Value() Over()和Last_Value() Over()的使用

WITH PM (fName,fClass,fScore,fLow,fHigh) AS
(
  Select fName,fClass,fScore,
    First_Value(fScore) Over(Partition By fClass Order By fScore Rows BETWEEN unbounded Preceding AND unbounded Following) Low,
    Last_Value(fScore)  Over(Partition By fClass Order By fScore Rows BETWEEN unbounded Preceding AND unbounded Following) High
  from Test
)
SELECT * FROM PM;
-- 运行结果
fName fClass  fScore  fLow  fHigh
-----------------------------------
XS004   11      80     80    95
XS002   11      95     80    95
XS003   11      95     80    95
XS001   12      74     74    92
XS005   12      92     74    92
XS008   13      45     45    99
XS009   13      55     45    99
XS010   13      78     45    99
XS006   13      99     45    99
XS007   13      99     45    99
-----------------------------------

示例07: 忽略空值[ORACLE中使用限定符 ignore Nulls]

-- For Oracle 11G 在First_Value中增加 ignore Nulls
  WITH PM (fName,fClass,fScore,fClassName,fCN) AS
  (
    Select fName,fClass,fScore,fClassName,
      First_Value(fClassName ignore Nulls) Over(Order By fClass) fCN
    from Test
  )
  SELECT * FROM PM;

-- For SQL Server 2012,没有限定符 ignore Nulls
WITH PM (fName,fClass,fScore,fClassName,fCN) AS
(
  Select fName,fClass,fScore,fClassName,
    First_Value(fClassName) Over(Order By fClass) fCN
  from Test
)
SELECT * FROM PM;
-- 运行结果
fName fClass  fScore  fClassName        fCN
----------------------------------------------------------
XS002   11      95    计算机系2020级1班 计算机系2020级1班
XS003   11      95    计算机系2020级1班 计算机系2020级1班
XS004   11      80    NULL              计算机系2020级1班
XS005   12      92    NULL              计算机系2020级1班
XS001   12      74    计算机系2020级2班 计算机系2020级1班
XS006   13      99    计算机系2020级3班 计算机系2020级1班
XS007   13      99    计算机系2020级3班 计算机系2020级1班
XS008   13      45    NULL              计算机系2020级1班
XS009   13      55    计算机系2020级3班 计算机系2020级1班
XS010   13      78    计算机系2020级3班 计算机系2020级1班
----------------------------------------------------------
说明: fCN列的内容都是'计算机系2020级1班'

-- For SQL Server 2012
  通过增加限定符'Partition'对'fClass'进行分组
WITH PM (fName,fClass,fScore,fClassName,fCN) AS
(
  Select fName,fClass,fScore,fClassName,
    First_Value(fClassName) Over(Partition By fClass Order By fClass) fCN
  from Test
)
SELECT * FROM PM;
-- 运行结果
fName fClass  fScore  fClassName        fCN
----------------------------------------------------------
XS002   11      95    计算机系2020级1班 计算机系2020级1班
XS003   11      95    计算机系2020级1班 计算机系2020级1班
XS004   11      80    NULL              计算机系2020级1班
XS005   12      92    NULL              NULL              --NULL在第一行时出现填充错误
XS001   12      74    计算机系2020级2班 NULL              --NULL在第一行时出现填充错误
XS006   13      99    计算机系2020级3班 计算机系2020级3班
XS007   13      99    计算机系2020级3班 计算机系2020级3班
XS008   13      45    NULL              计算机系2020级3班
XS009   13      55    计算机系2020级3班 计算机系2020级3班
XS010   13      78    计算机系2020级3班 计算机系2020级3班
----------------------------------------------------------
说明: 都是用第一行进行填充

示例08: 纵向或横向对比分析(上一年度)

访问相同结果集中先前行的数据,LAG 以当前行之前的给定物理偏移量来提供对行的访问。
在 SELECT 语句中使用此分析函数可将当前行中的值与先前行中的值进行纵向或横向比较。


--[部门ID,配额日期,销售配额]
With WW (fBusinessID,fQuotaDate,fSalesQuota) AS (
  Select 4,'2018-01-01',15000 UNION
  Select 5,'2018-01-01',15500 UNION
  Select 4,'2019-01-01',20000 UNION
  Select 5,'2019-01-01',25000 UNION
  Select 1,'2018-01-01',30000 UNION
  Select 1,'2019-01-01',30000 UNION
  Select 2,'2018-01-01',30000 UNION
  Select 2,'2019-01-01',25000
)
Select
  fBusinessID,
  Year(fQuotaDate) fSalesYear,
  fSalesQuota fCurrentQuota,
  LAG(fSalesQuota,1,0) Over(order by fBusinessID ASC,Year(fQuotaDate) ASC) fPreviousQuota
from WW;
-- 运行结果
fBusinessID fSalesYear  fCurrentQuota fPreviousQuota
----------------------------------------------------------
  1          2018        30000         0
  1          2019        30000         30000  -- 这行的分析有意义的
  2          2018        30000         30000
  2          2019        25000         30000  -- 这行的分析有意义的
  4          2018        15000         25000
  4          2019        20000         15000  -- 这行的分析有意义的
  5          2018        15500         20000
  5          2019        25000         15500  -- 这行的分析有意义的
----------------------------------------------------------

-- 加入了分区的限定符(Partition By fBusinessID,使得分析更有意义)
With WW (fBusinessID,fQuotaDate,fSalesQuota) AS (
  Select 4,'2018-01-01',15000 UNION
  Select 5,'2018-01-01',15500 UNION
  Select 4,'2019-01-01',20000 UNION
  Select 5,'2019-01-01',25000 UNION
  Select 1,'2018-01-01',30000 UNION
  Select 1,'2019-01-01',30000 UNION
  Select 2,'2018-01-01',30000 UNION
  Select 2,'2019-01-01',25000
)
Select
  fBusinessID,
  Year(fQuotaDate) fSalesYear,
  fSalesQuota fCurrentQuota,
  LAG(fSalesQuota,1,0) Over(Partition By fBusinessID order by Year(fQuotaDate)) PreviousQuota
from WW;
-- 运行结果
fBusinessID fSalesYear  fCurrentQuota fPreviousQuota
----------------------------------------------------------
  1          2018        30000        0
  1          2019        30000        30000  -- 这家商店的配额持平
  2          2018        30000        0
  2          2019        25000        30000  -- 这家商店的配额增加了
  4          2018        15000        0
  4          2019        20000        15000  -- 这家商店的配额减少了
  5          2018        15500        0
  5          2019        25000        15500  -- 这家商店的配额减少了
----------------------------------------------------------

示例09: 纵向或横向对比分析(下一年度)

访问相同结果集中先前行的数据,LEAD 以当前行之前的给定物理偏移量来提供对行的访问。
在 SELECT 语句中使用此分析函数可将当前行中的值与先前行中的值进行纵向或横向比较。


--[部门ID,配额日期,销售配额]
With WW (fBusinessID,fQuotaDate,fSalesQuota) AS (
  Select 4,'2018-01-01',15000 UNION
  Select 5,'2018-01-01',15500 UNION
  Select 4,'2019-01-01',20000 UNION
  Select 5,'2019-01-01',25000 UNION
  Select 1,'2018-01-01',30000 UNION
  Select 1,'2019-01-01',30000 UNION
  Select 2,'2018-01-01',30000 UNION
  Select 2,'2019-01-01',25000
)
Select
  fBusinessID,
  Year(fQuotaDate) fSalesYear,
  fSalesQuota fCurrentQuota,
  LEAD(fSalesQuota,1,0) Over(order by fBusinessID ASC,Year(fQuotaDate) ASC) fNextQuota
from WW;
-- 运行结果
fBusinessID fSalesYear  fCurrentQuota fNextQuota
----------------------------------------------------------
  1          2018        30000         30000
  1          2019        30000         30000
  2          2018        30000         25000
  2          2019        25000         15000
  4          2018        15000         20000
  4          2019        20000         15500
  5          2018        15500         25000
  5          2019        25000         0
----------------------------------------------------------

-- 加入了分区的限定符(Partition By fBusinessID,使得分析更有意义)
With WW (fBusinessID,fQuotaDate,fSalesQuota) AS (
  Select 4,'2018-01-01',15000 UNION
  Select 5,'2018-01-01',15500 UNION
  Select 4,'2019-01-01',20000 UNION
  Select 5,'2019-01-01',25000 UNION
  Select 1,'2018-01-01',30000 UNION
  Select 1,'2019-01-01',30000 UNION
  Select 2,'2018-01-01',30000 UNION
  Select 2,'2019-01-01',25000
)
Select
  fBusinessID,
  Year(fQuotaDate) fSalesYear,
  fSalesQuota fCurrentQuota,
  LEAD(fSalesQuota,1,0) Over(Partition By fBusinessID order by Year(fQuotaDate)) fNextQuota
from WW;
-- 运行结果
fBusinessID fSalesYear  fCurrentQuota fNextQuota
----------------------------------------------------------
  1          2018        30000         30000
  1          2019        30000         0
  2          2018        30000         25000
  2          2019        25000         0
  4          2018        15000         20000
  4          2019        20000         0
  5          2018        15500         25000
  5          2019        25000         0
----------------------------------------------------------

PERCENT_RANK

计算中一组行内某行的相对排名。
使用 PERCENT_RANK 计算一个值在查询结果集或分区中的相对位置。
PERCENT_RANK 类似于 CUME_DIST 函数。

下面的示例使用 CUME_DIST 函数计算给定部门内每个雇员的薪金百分比。
CUME_DIST 函数返回的值表示薪金低于或等于同一个部门中当前雇员的雇员百分比。
PERCENT_RANK 函数将雇员的薪金在部门内的排名计算为一个百分比。
指定 PARTITION BY 子句来按部门对结果集中的行进行分区。
OVER 子句中的 ORDER BY 子句对每个分区中的行进行排序。
SELECT 语句中的 ORDER BY 子句对整个结果集中的行进行排序。


With WW (Department,LastName,salary) AS (
  SELECT '文档控制','Arifin',    17885 UNION
  SELECT '文档控制','Norred',    18269 UNION
  SELECT '文档控制','Kharatis',  19309 UNION
  SELECT '文档控制','Chai',      12500 UNION
  SELECT '文档控制','Berge',     12500 UNION
  SELECT '信息服务','Trenary',   54808 UNION
  SELECT '信息服务','Conroy',    36635 UNION
  SELECT '信息服务','Ajenstat',  34615 UNION
  SELECT '信息服务','Wilson',    34615 UNION
  SELECT '信息服务','Sharma',    34519 UNION
  SELECT '信息服务','Connelly',  34519 UNION
  SELECT '信息服务','Berg',      24038 UNION
  SELECT '信息服务','Meyyappan', 24038 UNION
  SELECT '信息服务','Bacon',     24038 UNION
  SELECT '信息服务','Bueno',     24038
)
SELECT Department, LastName, salary,
       CUME_DIST()    OVER (PARTITION BY Department ORDER BY salary) AS CumeDist,
       PERCENT_RANK() OVER (PARTITION BY Department ORDER BY salary) AS PctRank
FROM WW
ORDER BY Department, salary DESC;

Department  LastName   salary  CumeDist  PctRank
------------------------------------------------------------------------
文档控制    Kharatis   19309   1         1
文档控制    Norred     18269   0.8       0.75
文档控制    Arifin     17885   0.6       0.5
文档控制    Berge      12500   0.4       0
文档控制    Chai       12500   0.4       0
信息服务    Trenary    54808   1         1
信息服务    Conroy     36635   0.9       0.888888888888889
信息服务    Ajenstat   34615   0.8       0.666666666666667
信息服务    Wilson     34615   0.8       0.666666666666667
信息服务    Connelly   34519   0.6       0.444444444444444
信息服务    Sharma     34519   0.6       0.444444444444444
信息服务    Bacon      24038   0.4       0
信息服务    Berg       24038   0.4       0
信息服务    Bueno      24038   0.4       0
信息服务    Meyyappan  24038   0.4       0
------------------------------------------------------------------------
PERCENT_RANK 返回的值范围大于 0 并小于或等于 1。
任何一组中第一行的 PERCENT_RANK 都为 0。 默认情况下包含 NULL 值,且该值被视为最低的可能值。

PERCENTILE_CONT

下面的示例使用 PERCENTILE_CONT 和 PERCENTILE_DISC 函数找出每个部门内雇员的薪金中值。
这些函数可能返回不同的值。
PERCENTILE_CONT 内插适当的值,它在数据集中可能存在,也可能不存在,而 PERCENTILE_DISC 始终从数据集中返回实际值。


With WW (Department,LastName,salary) AS (
  SELECT '文档控制','Arifin',    17885 UNION
  SELECT '文档控制','Norred',    18269 UNION
  SELECT '文档控制','Kharatis',  19309 UNION
  SELECT '文档控制','Chai',      12500 UNION
  SELECT '文档控制','Berge',     12500 UNION
  SELECT '信息服务','Trenary',   54808 UNION
  SELECT '信息服务','Conroy',    36635 UNION
  SELECT '信息服务','Ajenstat',  34615 UNION
  SELECT '信息服务','Wilson',    34615 UNION
  SELECT '信息服务','Sharma',    34519 UNION
  SELECT '信息服务','Connelly',  34519 UNION
  SELECT '信息服务','Berg',      24038 UNION
  SELECT '信息服务','Meyyappan', 24038 UNION
  SELECT '信息服务','Bacon',     24038 UNION
  SELECT '信息服务','Bueno',     24038
)
SELECT DISTINCT Department,
       PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) OVER (PARTITION BY Department) AS MedianCont,
       PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY salary) OVER (PARTITION BY Department) AS MedianDisc
FROM WW

-- 薪金中值
Department  MedianCont  MedianDisc
-----------------------------------
文档控制    17885       17885
信息服务    34519       34519
-----------------------------------

你可能感兴趣的:(--------1.2,SQL,SERVER)