总结SQL Server窗口函数的简单使用

本文涉及的产品
云数据库 RDS SQL Server,独享型 2核4GB
简介:
一、从一个熟悉的示例说起
我们熟知的数据库分页查询,以 这一篇介绍过的为例吧。分页查询Person表中的人,可以这么写SQL语句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
WITH  Record AS  (
         SELECT
         Row_Number() OVER ( ORDER  BY  Id DESC ) AS  RecordNumber,
         Id,
         FirstName,
         LastName,
         Height,
         Weight
     FROM
         Person (NOLOCK)
     )
     SELECT
     RecordNumber,
     ( SELECT  COUNT (0) FROM  Record) AS  TotalCount,
     Id,
         FirstName,
         LastName,
         Height,
         Weight
     FROM  Record
     WHERE  RecordNumber BETWEEN  1 AND  10
其中, ROW_NUMBER()是排名函数,而紧随其后的  OVER()函数就是窗口函数。
你还在用二次top方式的分页查询吗?可以考虑尝试使用排名函数配合CTE实现分页。
 
二、窗口函数
本文介绍窗口函数,以下面的学生成绩表为例:
1
2
3
4
5
6
7
8
CREATE  TABLE  [StudentScore](
     [Id] [ int ] IDENTITY(1,1) NOT  NULL ,
     [StudentId] [ int ] NOT  NULL  CONSTRAINT  [DF_StudentScore_StudentId]  DEFAULT  ((0)),
     [ClassId] [ int ] NOT  NULL  CONSTRAINT  [DF_StudentScore_ClassId]  DEFAULT  ((0)),
     [CourseId] [ int ] NOT  NULL  CONSTRAINT  [DF_StudentScore_CourseId]  DEFAULT  ((0)),
     [Score] [ float ] NOT  NULL  CONSTRAINT  [DF_StudentScore_Score]  DEFAULT  ((0)),
     [CreateDate] [datetime] NOT  NULL  CONSTRAINT  [DF_StudentScore_CreateDate]  DEFAULT  (getdate())
) ON  [ PRIMARY ]
其中,Id是自增Id,CreateDate是录入时间, StudentId 学生,ClassId 班级,CourseId  课程 ,Score  分数
录入一些测试数据如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
--CourseId 2:语文 4:数学 8:英语
 
--1班学生成绩
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (1,1,2,85)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (2,1,2,95.5)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (3,1,2,90)
 
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (1,1,4,90)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (2,1,4,98)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (3,1,4,89)
 
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (1,1,8,80)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (2,1,8,75.5)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (3,1,8,77)
 
 
--2班学生成绩
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (1,2,2,90)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (2,2,2,77)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (3,2,2,78)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (4,2,2,83)
 
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (1,2,4,98)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (2,2,4,95)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (3,2,4,78)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (4,2,4,100)
 
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (1,2,8,85)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (2,2,8,90)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (3,2,8,86)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (4,2,8,78.5)
 
--3班学生成绩
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (1,3,2,82)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (2,3,2,78)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (3,3,2,91)
 
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (1,3,4,83)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (2,3,4,78)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (3,3,4,99)
 
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (1,3,8,86)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (2,3,8,78)
INSERT  INTO  StudentScore(StudentId,ClassId,CourseId,Score) VALUES  (3,3,8,97)
窗口函数是SQL Server2005新增的函数。下面就谈谈它的基本概念:
1、窗口函数的作用
窗口函数是对一组值进行操作,不需要使用GROUP BY 子句对数据进行分组,还能够在同一行中同时返回基础行的列和聚合列。举例来说,我们要得到一个年级所有班级所有学生的平均分,按照传统的写法,我们肯定是通过AVG聚合函数来实现求平均分。这样带来的”坏处“是我们不能轻松地返回基础行的列(班级,学生等列),而只能得到聚合列。因为聚合函数的要点就是对一组值进行聚合,以GROUP BY 查询作为操作的上下文,由于GROUP BY 操作对数据进行分组后,查询为每个组只返回一行数据,因此,要限制所有表达式为每个组只返回一个值。而通过窗口函数,基础列和聚合列的查询都轻而易举。
2、基本语法
OVER([PARTITION BY value_expression,..[n] ] <ORDER BY BY_Clause>)
窗口函数使用OVER函数实现,OVER函数分带参和不带参两种。其中可选参数PARTITION BY用于将数据按照特定字段分组。
3、简单示例
查询学生成绩表的基本列以及所有班级所有学生的语文平均分:
1
2
3
4
5
6
7
8
9
10
11
SELECT
     --Id,
     --CreateDate,
     StudentId,
     ClassId,
     CourseId,
     Score,
    CAST ( AVG (Score) OVER() AS  decimal (5,2) ) AS   '语文平均分'
FROM
     StudentScore
     WHERE  CourseId=2
结果如下:
example1
4、PARTITION BY
如果我们需要查询每一个班级的语文平均分,可以根据PARTION BY来进行分组:
1
2
3
4
5
6
7
8
9
10
11
SELECT
     Id,
     CreateDate,
     StudentId,
     ClassId,
     CourseId,
     Score,
    CAST ( AVG (Score) OVER(PARTITION BY  ClassId ) AS  decimal (5,2) ) AS   '语文平均分'
FROM
     StudentScore
     WHERE  CourseId=2
查询结果如下:
example2
图可能不清楚,三个班级的语文平均分是不同的。
到这里,其实你可能已经体会到使用OVER函数的好处了:
a、OVER子句的优点就是能够在返回基本列的同时,在同一行对它们进行聚合
b、可以在表达式中混合使用基本列和聚合列
如果我们使用传统的GROUP BY分组查询,直接获取基本列和聚合列就不是这么简单一句SQL了。
如你所知,我们知道的很多聚合函数,如SUM,AVG,MAX,MIN等聚合函数都支持窗口函数的运算。
 
二、让人爱不释手的排名函数
SQL Server提供了4个排名函数:ROW_NUMBER(), RANK(),DENSE_RANK()和NTILE()。下面通过示例重点谈谈这四个函数的使用。
1、ROW_NUMBER()
返回结果集分区内行的序列号,每个分区的第一行从 1 开始。ORDER BY 子句可确定在特定分区中为行分配唯一 ROW_NUMBER 的顺序。
下面的查询按照数学成绩逆序排列:
1
2
3
4
5
6
7
8
9
10
11
SELECT
     Id,
--    CreateDate,
     ROW_NUMBER() OVER( ORDER  BY  Score DESC ) AS  '序号' ,
     StudentId,
     ClassId,
     CourseId,
     Score
FROM
     StudentScore
     WHERE  CourseId=8
结果如下:
example3
据我所知,此函数在SQL Server分页查询中几乎已经普及应用。Good job。
 
2、RANK()和DENSE_RANK()
(1)、 RANK()函数
返回结果集的分区内每行的排名。行的排名是相关行之前的排名数加一。如果两个或多个行与一个排名关联,则每个关联行将得到相同的排名。
1
2
3
4
5
6
7
8
9
10
11
SELECT
     Id,
--    CreateDate,
     RANK() OVER( ORDER  BY  Score DESC ) AS  '序号' ,
     StudentId,
     ClassId,
     CourseId,
     Score
FROM
     StudentScore
     WHERE  CourseId=8
结果如下:
example5
注意,它和ROW_NUMBER()的异同点,您应该已经知道了:
a、RANK函数和ROW_NUMBER函数类似,它们都是用来对结果进行排序。
b、不同的是,ROW_NUMBER函数为每一个值生成唯一的序号,而RANK函数为相同的值生成相同的序号。
上图中,两个86分的学生对应的序号都是 3,而接着排在它们下面的序号直接变成了 5
(2)、 DENSE_RANK()函数
返回结果集分区中行的排名,在排名中没有任何间断。行的排名等于所讨论行之前的所有排名数加一。如果有两个或多个行受同一个分区中排名的约束,则每个约束行将接收相同的排名。
1
2
3
4
5
6
7
8
9
10
11
SELECT
     Id,
--    CreateDate,
     DENSE_RANK() OVER( ORDER  BY  Score DESC ) AS  '序号' ,
     StudentId,
     ClassId,
     CourseId,
     Score
FROM
     StudentScore
     WHERE  CourseId=8
查询结果如下:
example6
上图中,两个86分的学生对应的序号都是 3,而接着排在它们下面的序号是 4(也就是说DENSE_RANK()函数查询的序号是类似ROW_NUMBER()那样连续的,但是对于相同值的行生成相同的序号,从这一点上来说,对于相同查询条件和排序的查询,ROW_NUMBER()函数查询的结果集是DENSE_RANK()函数查询的结果的子集)。这也是我们可以总结出的RANK和DENSE_RANK()这两个函数的最大的不同点。
3、NTILE()
NTILE函数把结果中的行关联到组,并为每一行分配一个所属的组的编号,编号从一开始。对于每一个行,NTILE 将返回此行所属的组的编号。
如果分区的行数不能被 integer_expression 整除,则将导致一个成员有两种大小不同的组。按照 OVER 子句指定的顺序,较大的组排在较小的组前面。
1
2
3
4
5
6
7
8
9
10
11
SELECT
     Id,
--    CreateDate,
     NTILE(6) OVER( ORDER  BY  ClassId DESC ) AS  '组编号' ,
     StudentId,
     ClassId,
     CourseId,
     Score
FROM
     StudentScore
     WHERE  CourseId=8
查询的结果如下:
example4
 
本文的介绍和示例都很基础,但是通过窗口函数,确实可以帮我们优化很多复杂查询。上面的SQL语句看上去每一个都很简单,但是现在的简单都隐藏着背后的复杂。需要提醒的是,分组概念虽然基础却很重要,你必须掌握;而熟练应用了窗口函数,你的SQL查询就如虎添翼更上层楼了。
最后,我一直担心对于海量数据,SQL Server的性能问题。因为近期的开发碰巧遇到海量数据的查询,最多的过亿,数据量最少的一个表,也过5000万,不知道用了分区表性能有没有明显提升。







本文转自JeffWong博客园博客,原文链接:http://www.cnblogs.com/jeffwongishandsome/archive/2010/12/04/1896672.html,如需转载请自行联系原作者

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
目录
相关文章
|
14天前
|
SQL 人工智能 算法
【SQL server】玩转SQL server数据库:第二章 关系数据库
【SQL server】玩转SQL server数据库:第二章 关系数据库
52 10
|
1月前
|
SQL 数据库 数据安全/隐私保护
Sql Server数据库Sa密码如何修改
Sql Server数据库Sa密码如何修改
|
24天前
|
SQL
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
17 0
|
14天前
|
SQL 算法 数据库
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
88 6
|
1天前
|
SQL 数据管理 关系型数据库
如何在 Windows 上安装 SQL Server,保姆级教程来了!
在Windows上安装SQL Server的详细步骤包括:从官方下载安装程序(如Developer版),选择自定义安装,指定安装位置(非C盘),接受许可条款,选中Microsoft更新,忽略警告,取消“适用于SQL Server的Azure”选项,仅勾选必要功能(不包括Analysis Services)并更改实例目录至非C盘,选择默认实例和Windows身份验证模式,添加当前用户,最后点击安装并等待完成。安装成功后关闭窗口。后续文章将介绍SSMS的安装。
4 0
|
3天前
|
SQL 关系型数据库 MySQL
:“You have an error in your SQL syntax; check the manual that corresponds to your MySQL server versi
:“You have an error in your SQL syntax; check the manual that corresponds to your MySQL server versi
13 0
|
10天前
|
SQL 安全 网络安全
IDEA DataGrip连接sqlserver 提示驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接的解决方法
IDEA DataGrip连接sqlserver 提示驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接的解决方法
21 0
|
15天前
|
SQL 存储 数据挖掘
数据库数据恢复—RAID5上层Sql Server数据库数据恢复案例
服务器数据恢复环境: 一台安装windows server操作系统的服务器。一组由8块硬盘组建的RAID5,划分LUN供这台服务器使用。 在windows服务器内装有SqlServer数据库。存储空间LUN划分了两个逻辑分区。 服务器故障&初检: 由于未知原因,Sql Server数据库文件丢失,丢失数据涉及到3个库,表的数量有3000左右。数据库文件丢失原因还没有查清楚,也不能确定数据存储位置。 数据库文件丢失后服务器仍处于开机状态,所幸没有大量数据写入。 将raid5中所有磁盘编号后取出,经过硬件工程师检测,没有发现明显的硬件故障。以只读方式将所有磁盘进行扇区级的全盘镜像,镜像完成后将所
数据库数据恢复—RAID5上层Sql Server数据库数据恢复案例
|
18天前
|
SQL 数据安全/隐私保护
SQL Server 2016安装教程
SQL Server 2016安装教程
21 1
|
18天前
|
SQL 安全 Java
SQL server 2017安装教程
SQL server 2017安装教程
17 1