开窗函数支持分区、排序和框架三种元素,其语法格式如下:
OVER (
[ ]
[ ]
[ ]
)
就是将窗口指定列具有相同值的那些行进行分区,分区与分组比较类似,但是分组指定后对于整个SELECT语句只能按照这个分组,不过
分区可以在一条语句中指定不同的分区。
::=
PARTITION BY value_expression , ... [ n ]
分区之后可以指定排序列,那么在窗口计算之前,各个窗口的行的逻辑顺序将确定。
::=
ORDER BY order_by_expression
[ COLLATE collation_name ]
[ ASC | DESC ]
[ ,...n ]
框架是对窗口进行进一步的分区,框架有两种范围限定方式:一种是使用Rows子句,通过指定当前行之前或之后的固定数目的行来限制分区中的行数;
另一种是Range子句,按照排序列的当前值,根据相同值来确定分区中的行数。
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;
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 表示指定取值范围为:当前行与前一行的值,默认到当前行.
-- 查询各个班级的第一名:分组排名
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
-----------------------------------
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
-----------------------------------
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分组的求和
-----------------------------------
–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
-----------------------------------
-- 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班
----------------------------------------------------------
说明: 都是用第一行进行填充
访问相同结果集中先前行的数据,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 -- 这家商店的配额减少了
----------------------------------------------------------
访问相同结果集中先前行的数据,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 类似于 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_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
-----------------------------------