序言

 设计窗口函数目的?

  在开窗函数出现之前存在着很多用 SQL 语句很难解决的问题,很多都要通过复杂的相关子查询或者存储过程来完成。

  为了解决这些问题,在 2003 年 ISO SQL 标准加入了开窗函数,开窗函数的使用使得这些经典的难题可以被轻松的解决。

  SQL Server 2012之后对窗口函数进行了极大的加强,但对于很多开发人员来说,对窗口函数却不甚了解,导致了这样强大的功能被浪费。

  开窗函数可以优雅的部分取代分组查询和子查询。

 什么是窗口函数?

  可以看到与聚合函数不同的是,开窗函数在聚合函数后增加了一个 OVER 关键字。

  开窗函数格式: 函数名(列) OVER(选项)

  OVER 关键字表示把函数当成开窗函数而不是聚合函数。SQL 标准允许将所有聚合函数用做开窗函数,使用 OVER 关键字来区分这两种用法。

  在上边的例子中,开窗函数 COUNT(*) OVER()对于查询结果的每一行都返回所有符合条件的行的条数。OVER 关键字后的括号中还经常添加选项用以改变进行聚合运算的窗口范围。如果 OVER 关键字后的括号中的选项为空,则开窗函数会对结果集中的所有行进行聚合运算。

  窗口函数的典型范例是我们在SQL Server 2005之后用到的排序函数,比如代码清单1所示。

Row_Number() OVER (partition by xx ORDER BY xxx desc) RowNumber

  因此,我们可以把窗口函数的语法抽象出来,如代码清单2所示。

函数() Over (PARTITION By 列1,列2,Order By 列3,窗口子句) AS 列别名

示例一

查询姓名,性别,该性别所有员工的总数

如果我们使用传统的写法,那一定会涉及到子查询,虽然使用子查询能够解决这个问题,但是子查询的使用非常麻烦,使用开窗函数则可以大大简化实现。

SELECT s.Sname,s.Ssex, (SELECT COUNT(*) FROM dbo.Student a WHERE a.Ssex=s.Ssex) AS GenderTotal  FROM  dbo.Student s

如果我们使用了窗口函数,代码瞬间就变得简洁,不再需要子查询或Join。

SELECT s.Sname,s.Ssex, COUNT(*) OVER (PARTITION BY Ssex) GenderTotal  FROM  dbo.Student s

查询结果

假如我们考虑更复杂的例子,在Over子句加上了Order By,来完成一个平均数累加,如果不使用窗口函数,那一定是游标,循环等麻烦的方式,如果使用了窗口函数,则一切就变得非常轻松。

Partition By

  代码清单2展示了窗口函数的语法,其中Over子句之后第一个提到的就是Partition By。Partition By子句也可以称为查询分区子句,非常类似于Group By,都是将数据按照边界值分组,而Over之前的函数在每一个分组之内进行,如果超出了分组,则函数会重新计算,比如上图中的例子,我们将数据分为男性和女性两部分,前面的Count()函数针对这两组分别计算值(男性3,女性3)。

针对Partition By可以应用的函数不仅仅是我们所熟知的聚合函数,以及一些其他的函数,比如说Row_Number()。

示例二

SELECT [UserName]
,[Subject]
,[Score]
FROM [MyDataBase].[dbo].[StudentScores]

数据

SELECT  [UserName] ,
[Subject] ,
[Score] ,
COUNT(*) OVER ( PARTITION BY UserName ) AS totalcount
FROM [MyDataBase].[dbo].[StudentScores];

COUNT(*) OVER ( PARTITION BY UserName ) AS totalcount

SELECT  [UserName] ,
[Subject] ,
[Score] ,
COUNT(*) OVER ( PARTITION BY Subject ) AS totalcount
FROM [MyDataBase].[dbo].[StudentScores];

COUNT(*) OVER ( PARTITION BY Subject ) AS totalcount

SELECT  [UserName] ,
[Subject] ,
[Score] ,
COUNT(*) OVER ( PARTITION BY Score ) AS totalcount
FROM [MyDataBase].[dbo].[StudentScores];

COUNT(*) OVER ( PARTITION BY Score ) AS totalcount

示例三

USE [MyDataBase]
GO
/****** Object: Table [dbo].[T_Person] Script Date: 2019/2/11 15:26:15 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[T_Person](
[FName] [varchar](20) NULL,
[FCity] [varchar](20) NULL,
[FAge] [int] NULL,
[FSalary] [int] NULL
) ON [PRIMARY] GO
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Tom', N'BeiJing', 20, 3000)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Tim', N'ChengDu', 21, 4000)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Jim', N'BeiJing', 22, 3500)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Lily', N'London', 21, 2000)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'John', N'NewYork', 22, 1000)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'YaoMing', N'BeiJing', 20, 3000)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Swing', N'London', 22, 2000)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Guo', N'NewYork', 20, 2800)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'YuQian', N'BeiJing', 24, 8000)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Ketty', N'London', 25, 8500)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Kitty', N'ChengDu', 25, 3000)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Merry', N'BeiJing', 23, 3500)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Smith', N'ChengDu', 30, 3000)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Bill', N'BeiJing', 25, 2000)
INSERT [dbo].[T_Person] ([FName], [FCity], [FAge], [FSalary]) VALUES (N'Jerry', N'NewYork', 24, 3300)

数据

比如我们想查询每个工资小于 5000 元的员工信息(城市以及年龄),并且在每行中都显示所有工资小于 5000 元的员工个数:

select fname,fcity,fsalary,(select count(*) from t_person where fsalary < 5000)
from dbo.t_person
where fsalary < 5000

使用子查询

select fname, fcity, fsalary, count(*) over()
from t_person
where fsalary < 5000

使用开窗函数

PARTITION BY 子句:

开窗函数的 OVER 关键字后括号中的可以使用 PARTITION BY 子句来定义行的分区来供进行聚合计算。与 GROUP BY 子句不同,PARTITION BY 子句创建的分区是独
立于结果集的,创建的分区只是供进行聚合计算的,而且不同的开窗函数所创建的分区也不互相影响。下面的 SQL 语句用于显示每一个人员的信息以及所属城市的人员数:

select fname,fcity,fage,fsalary,count(*) over(partition by fcity) 所在城市人数 from dbo.t_person

COUNT(*) OVER(PARTITION BY FCITY)表示对结果集按照FCITY进行分区,并且计算当前行所属的组的聚合计算结果。比如对于FName等于 Tom的行,它所属的城市是BeiJing,同属于BeiJing的人员一共有6个,所以对于这一列的显示结果为6。

在同一个SELECT语句中可以同时使用多个开窗函数,而且这些开窗函数并不会相互干扰。比如下面的SQL语句用于显示每一个人员的信息、所属城市的人员数以及同龄人的人数:

--显示每一个人员的信息、所属城市的人员数以及同龄人的人数:
select fname,
fcity,
fage,
fsalary,
count(*) over(partition by fcity) 所属城市的人个数,
count(*) over(partition by fage) 同龄人个数
from dbo.t_person

ORDER BY子句:

开窗函数中可以在OVER关键字后的选项中使用ORDER BY子句来指定排序规则,而且有的开窗函数还要求必须指定排序规则。使用ORDER BY子句可以对结果集按
照指定的排序规则进行排序,并且在一个指定的范围内进行聚合运算。ORDER BY子句的语法为:

ORDER BY 字段名 RANGE|ROWS BETWEEN 边界规则1 AND 边界规则2

RANGE表示按照值的范围进行范围的定义,而ROWS表示按照行的范围进行范围的定义;边界规则的可取值见下表:

“RANGE|ROWS BETWEEN 边界规则1 AND 边界规则2”部分用来定位聚合计算范围,这个子句又被称为定位框架。

例子程序一:查询从第一行到当前行的工资总和:

select fname,
fcity,
fage,
fsalary,
sum(fsalary) over(order by fsalary rows between unbounded preceding and current row) 到当前行工资求和
from dbo.t_person

这里的开窗函数“SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN
UNBOUNDED PRECEDING AND CURRENT ROW)”表示按照FSalary进行排序,然后计算从第
一行(UNBOUNDED PRECEDING)到当前行(CURRENT ROW)的和,这样的计算结果就是按照
工资进行排序的工资值的累积和。

“RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW”是开窗函数中最常使用的定位框架,为了简化使用,如果使用的是这种定位框架,则可以省略定位框架声明部分,也就是说上边的sql可以简化成:

select fname,
fcity,
fage,
fsalary,
sum(fsalary) over(order by fsalary)
from dbo.t_person

https://www.cnblogs.com/lihaoyang/p/6756956.html

优点

现在我们对窗口函数有了初步的概览,文章后我会提供一些具体的例子来让对窗口函数的概念更加深刻,窗口函数除了上面提到的输入行等于输出行之外,还有如下特性和好处:

1、类似Group By的聚合

2、非顺序的访问数据

3、可以对于窗口函数使用分析函数、聚合函数和排名函数

4、简化了SQL代码(消除Join)

5、消除中间表

窗口函数是整个SQL语句最后被执行的部分,这意味着窗口函数是在SQL查询的结果集上进行的,因此不会受到Group By, Having,Where子句的影响。

缺点

资料

https://blog.csdn.net/mzl87/article/details/84455076

https://www.imooc.com/article/25447

SQL Server进阶 窗口函数的更多相关文章

  1. SQL Server 进阶 01 数据库的设计

    SQL Server 进阶 01 数据库的设计 本篇目录 课程内容回顾及介绍 为什么需要规范的数据库设计 设计数据库的步骤 绘制E-R(实体-关系)图 实体-关系模型 如何将E-R图转换为表 数据规范 ...

  2. 【目录】sql server 进阶篇系列

    随笔分类 - sql server 进阶篇系列 sql server 下载安装标记 摘要: SQL Server 2017 的各版本和支持的功能 https://docs.microsoft.com/ ...

  3. SQL Server进阶(十二)常用函数

    在SQL 2012基础教程中列出子句是按照以下顺序进行逻辑处理. FROM WHERE GROUP BY HAVING SELECT ORDER BY FROM TableName WHERE Use ...

  4. SQL Server进阶(十二)函数

    概述 函数有且只有一个输入参数和一个返回值,而存储过程没有这个限制: 返回表变量的函数可以当做VIEW或者临时表用在WHERE/HAVING/SELECT/JOIN语句中而存储过程不可以: 存储过程中 ...

  5. SQL Server进阶(十一)临时表、表变量

    临时表 本地临时表 适合开销昂贵   结果集是个非常小的集合 -- Local Temporary Tables IF OBJECT_ID('tempdb.dbo.#MyOrderTotalsByYe ...

  6. SQL Server进阶(五)子查询

    概述 子查询的概念: 当一个查询是另一个查询的条件时,称之为子查询.子查询可以嵌套在主查询中所有位置,包括SELECT.FROM.WHERE.GROUP BY.HAVING.ORDER BY. 外面的 ...

  7. SQL Server进阶(六)表表达式--派生表、公用表表达式(CTE)、视图和内联表值函数

    概述 表表达式是一种命名的查询表达式,代表一个有效地关系表.可以像其他表一样,在数据处理中使用表表达式. SQL Server支持四种类型的表表达式:派生表,公用表表达式,视图和内联表值函数. 为什么 ...

  8. SQL Server进阶(七)集合运算

    概述 为什么使用集合运算: 在集合运算中比联接查询和EXISTS/NOT EXISTS更方便. 并集运算(UNION) 并集:两个集合的并集是一个包含集合A和B中所有元素的集合. 在T-SQL中.UN ...

  9. SQL Server进阶(二)字段类型

    概述 系统数据类型详情 SqlDbType namespace System.Data { // // 摘要: // 指定要用于 System.Data.SqlClient.SqlParameter ...

随机推荐

  1. 线段树 by yyb

    线段树 by yyb Type1 维护特殊信息 1.[洛谷1438]无聊的数列 维护一个数列,两种操作 1.给一段区间加上一个等差数列 2.单点询问值 维护等差数列 不难发现,等差数列可以写成\(ad ...

  2. A.01.02—模块的输入—高端输入

    高端输入即一个高电平信号输入到模块,模块采样时最典型的是采用下拉电阻采样,当然,还有限流电阻和分压电阻,具体可以参见实际电路. 高端输入在汽车上用得不多,这种类型的输入既可以是开关提供的也可以是模块提 ...

  3. Typescript学习笔记(二)枚举

    跟随handbook的脚步,详细介绍一下枚举. enum Direction { Up = 1, Down, Left, Right } 一个枚举类型可以包含零个或多个枚举成员,每个枚举成员可以是一个 ...

  4. Qt5应用改变窗口大小时出现黑影

    解决方法 在启动程序时,添加-platform wayland参数 添加QT_QPA_PLATFORM=wayland-egl到系统环境变量 注意:改完后虽然没有黑影,但软件图标显示不正常,也不能正常 ...

  5. 利用selenium并使用gevent爬取动态网页数据

    首先要下载相应的库 gevent协程库:pip install gevent selenium模拟浏览器访问库:pip install selenium selenium库相应驱动配置  https: ...

  6. 团体程序设计天梯赛(CCCC) L3012 水果忍者 上凸或下凹的证明

    团体程序设计天梯赛代码.体现代码技巧,比赛技巧.  https://github.com/congmingyige/cccc_code #include <cstdio> #include ...

  7. poj 2229 Sumsets(记录结果再利用的DP)

    传送门 https://www.cnblogs.com/violet-acmer/p/9852294.html 题意: 将一个数N分解为2的幂之和共有几种分法? 题解: 定义dp[ i ]为数 i 的 ...

  8. testng优化:失败重跑,extentReport+appium用例失败截图,测试报告发邮件

    生成的单html方便jenkins集成发邮件,= = 构建失败发邮件 参考:https://blog.csdn.net/galen2016/article/details/77975965 步骤: 1 ...

  9. BZOJ4627 前缀和 + 权值线段树

    https://www.lydsy.com/JudgeOnline/problem.php?id=4627 题意:求序列中和在L到R之间的字串种数. 要求的是和的范围,我们可以考虑先求一个前缀和pre ...

  10. CodeForces1073E 数位dp+状压dp

    http://codeforces.com/problemset/problem/1073/E 题意 给定K,L,R,求L~R之间最多不包含超过K个数码的数的和. 显然这是一道数位dp,在做的过程中会 ...