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

前言:我一直十分喜欢使用SQL Server2005/2008的窗口函数,排名函数ROW_NUMBER()尤甚。今天晚上我在查看SQL Server开发的相关文档,整理收藏夹发现了两篇收藏已久的好文,后知后觉,读后又有点收获,顺便再总结一下。

一、从一个熟悉的示例说起
我们熟知的数据库分页查询,以这一篇介绍过的为例吧。分页查询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
结果如下:
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
查询结果如下:
图可能不清楚,三个班级的语文平均分是不同的。
到这里,其实你可能已经体会到使用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
结果如下:
据我所知,此函数在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
结果如下:
注意,它和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
查询结果如下:
上图中,两个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
查询的结果如下:
 
本文的介绍和示例都很基础,但是通过窗口函数,确实可以帮我们优化很多复杂查询。上面的SQL语句看上去每一个都很简单,但是现在的简单都隐藏着背后的复杂。需要提醒的是,分组概念虽然基础却很重要,你必须掌握;而熟练应用了窗口函数,你的SQL查询就如虎添翼更上层楼了。
最后,我一直担心对于海量数据,SQL Server的性能问题。因为近期的开发碰巧遇到海量数据的查询,最多的过亿,数据量最少的一个表,也过5000万,不知道用了分区表性能有没有明显提升。
参考文章:
 

总结SQL Server窗口函数的简单使用的更多相关文章

  1. SQL Server授权购买简单介绍

    SQL Server授权购买简单介绍 之前有同事问我,使用盗版序列号的SQL Server到底有没有性能限制,之前本人一直没有深入研究过,后来经过一番资料搜集和查证,汇总成这篇文章 微软的SQL Se ...

  2. (4.34)sql server窗口函数

    关键词:sql server窗口函数,窗口函数,分析函数 如果分析函数不可用,那么可能是版本还不支持 Window Function 包含了 4 个大类.分别是: 1 - Rank Function ...

  3. SQL Server Profiler的简单使用

    SQL Server Profiler可以检测在数据上执行的语句,特别是有的项目不直接使用sql语句,直接使用ORM框架的系统处理数据库的项目,在调试sql语句时,给了很大的帮助. 之前写了使用SQL ...

  4. SQL Server Service Broker 简单例子 (转)

    SQL Server Service Broker服务体系结构 消息类型 — 定义应用程序间交换的消息的名称.还可以选择是否验证消息.约定 — 指定给定会话中的消息方向和消息类型.队列 — 存储消息. ...

  5. SQL Server Profiler的简单使用(监控mssql)

    SQL Server Profiler可以检测在数据上执行的语句,特别是有的项目不直接使用sql语句,直接使用ORM框架的系统处理数据库的项目,在调试sql语句时,给了很大的帮助. 之前写了使用SQL ...

  6. mySql学习笔记:比sql server书写要简单

    在学mySql.总的感觉,mySql与Sql Server差不多,语法都很象,但mySql也许是吸取了SQL SERVER的一些经验,SQL语句书写起来更加简单. 比如说,设置主键.索引,SQL SE ...

  7. SQL Server窗口函数:ROWS与RANGE

    几乎每次我展示SQL Server里的窗口时,人们都非常有兴趣知道,当你定义你的窗口(指定的一组行)时,ROWS与RANGE选项之间的区别.因此在今天的文章里我想给你展示下这些选项的区别,对于你的分析 ...

  8. C#对于sql server数据库的简单操作

    1.在用windows模式登陆sql server 数据库 简历一个student的数据库,然后新建查询: create table student ( id int auto_increment p ...

  9. SQL SERVER数据库的简单介绍

    一.数据库技术的发展 数据库技术是应数据管理任务的需求而产生的,先后经历了人工管理.文件系统.数据库系统等三个阶段. 二.关系型数据库 SQL Server属于关系型数据库. 关系模型 以二维表来描述 ...

随机推荐

  1. Paint Tree

    题意: 给定一棵n个点的树,给定平面上n个点,将n个点用线段连起来画成树的形状,使得不存在不在端点相交的线段,构造出一种情况. 解法: 首先观察我们常规画出来的树形图可知,树的子树是根据极角分开的,这 ...

  2. HDOj-1425

    sort Time Limit: 6000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  3. 微信小程序开发之三元运算符代替wx.if/wx.else

    直接上代码 实现功能为:当fbphotoFirst为空时,src路径为“pic/信息反馈1-1_14.png“,并且点击事件uploadfbphotoFirst有效,否则为路径fbphotoFirst ...

  4. RXSwift01

    //创建 Observable 序列 func createObservable(){ /* let observable = Observable<Int>.just(5) let ob ...

  5. Codeforces Round #269 (Div. 2) A,B,C,D

    CodeForces - 471A 首先要有四个数相等,然后剩下两个数不同就是Bear,否则就是Elephant. #include <bits/stdc++.h> using names ...

  6. POJ3268【最短路】

    题意: n个点m条有向边,每个点有一头牛,每头牛会沿着各自的最短路先到x点,然后又从x点到各自的点,求这些牛中间最短路程最大的牛. 思路: 从x点到各点的最短路不用说了,裸的最短路: 但是从所有点到x ...

  7. Lightoj1028 【数学-乘法原理】

    题意: 给你一个数,问你有多少种进制对n的表示,存在后导零: 比如30:用3进制表示: 1010 思路: 我们发现,就是一个数的约数就能对n表示最后存在后导零: 计算[2 ,n]之间的n的约数个数. ...

  8. 如何快速将vc++的类转换为c#/cli

    所有需要的工具: 1. TextTemplate 2. P/Invoke Interop Assistant 3. DotNetResolver vc++的native类一般是无法直接暴露在.net环 ...

  9. 【BZOJ1174】: [Balkan2007]Toponyms

    →原题← ↑这样子我就不复制题面啦~ 就是一题很裸的字典树而已,不过空间卡的很死,直接开个数组 tr[N][52] 什么之类的一定会RE的(惨痛的教训) 当字典树空间不够而时间限制又比较宽松时,我们可 ...

  10. log4j.xml中Filter的用法

    前言 log4j中常用的Filter分为四种:DenyAllFilter.LevelMatchFilter.LevelRangeFilter.StringMatchFilter. 当appender匹 ...