SQL Server进阶 窗口函数
序言
设计窗口函数目的?
在开窗函数出现之前存在着很多用 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进阶 窗口函数的更多相关文章
- SQL Server 进阶 01 数据库的设计
SQL Server 进阶 01 数据库的设计 本篇目录 课程内容回顾及介绍 为什么需要规范的数据库设计 设计数据库的步骤 绘制E-R(实体-关系)图 实体-关系模型 如何将E-R图转换为表 数据规范 ...
- 【目录】sql server 进阶篇系列
随笔分类 - sql server 进阶篇系列 sql server 下载安装标记 摘要: SQL Server 2017 的各版本和支持的功能 https://docs.microsoft.com/ ...
- SQL Server进阶(十二)常用函数
在SQL 2012基础教程中列出子句是按照以下顺序进行逻辑处理. FROM WHERE GROUP BY HAVING SELECT ORDER BY FROM TableName WHERE Use ...
- SQL Server进阶(十二)函数
概述 函数有且只有一个输入参数和一个返回值,而存储过程没有这个限制: 返回表变量的函数可以当做VIEW或者临时表用在WHERE/HAVING/SELECT/JOIN语句中而存储过程不可以: 存储过程中 ...
- SQL Server进阶(十一)临时表、表变量
临时表 本地临时表 适合开销昂贵 结果集是个非常小的集合 -- Local Temporary Tables IF OBJECT_ID('tempdb.dbo.#MyOrderTotalsByYe ...
- SQL Server进阶(五)子查询
概述 子查询的概念: 当一个查询是另一个查询的条件时,称之为子查询.子查询可以嵌套在主查询中所有位置,包括SELECT.FROM.WHERE.GROUP BY.HAVING.ORDER BY. 外面的 ...
- SQL Server进阶(六)表表达式--派生表、公用表表达式(CTE)、视图和内联表值函数
概述 表表达式是一种命名的查询表达式,代表一个有效地关系表.可以像其他表一样,在数据处理中使用表表达式. SQL Server支持四种类型的表表达式:派生表,公用表表达式,视图和内联表值函数. 为什么 ...
- SQL Server进阶(七)集合运算
概述 为什么使用集合运算: 在集合运算中比联接查询和EXISTS/NOT EXISTS更方便. 并集运算(UNION) 并集:两个集合的并集是一个包含集合A和B中所有元素的集合. 在T-SQL中.UN ...
- SQL Server进阶(二)字段类型
概述 系统数据类型详情 SqlDbType namespace System.Data { // // 摘要: // 指定要用于 System.Data.SqlClient.SqlParameter ...
随机推荐
- optimize PHP-FPM优化
php-fpm进程pidpids=$(ps aux | grep ${process} | grep -v "grep" | awk '{print $2}') php-fpm 关 ...
- yii2 阿里云短信 aliyun-dysms
aliyun-dysms安装 composer require "saviorlv/yii2-dysms:dev-master" 或者添加下列代码在composer.json文件中 ...
- Mybatis 缓存失效的几种情况
1 不在同一个sqlSession对象中 下面比较下载同一个sqlSession和不在同一sqlSession下面的两种情况: 同一sqlSession: @Test public final voi ...
- request.getRequestDispatcher 页面跳转,样式丢失。
在页面中引用样式和其它资源的时候,尽量不要用相对路径,因为"当前路径"这个概念在J2EE中是不稳定的. 所以最好都是绝对路径,类似于: <% String cp = requ ...
- 使用Spring Cloud连接不同服务
http://www.infoq.com/cn/articles/spring-cloud-service-wiring 主要结论 Spring Cloud为微服务系统中相互依赖的服务提供了丰富的连接 ...
- 洛谷P1117 优秀的拆分
题意:求一个字符串中有多少形如AABB的子串. 解:嗯...我首先极度SB的想了一个后缀自动机套线段树启发式合并的做法,想必会TLE. 然后跑去看题解,发现实在是妙不可言... 显然要对每个位置求出向 ...
- 快速傅里叶变换(FFT)
扯 去北京学习的时候才系统的学习了一下卷积,当时整理了这个笔记的大部分.后来就一直放着忘了写完.直到今天都腊月二十八了,才想起来还有个FFT的笔记没整完呢.整理完这个我就假装今年的任务全都over了吧 ...
- java 8: ClassNotFoundException: sun.jdbc.odbc.JdbcOdbcDriver
转眼之间, java 11都快要推出了. 而我一直都在 java 7环境下写代码,真的不想升级,不想改变什么,可世界每天都在变化. 最近因为服务端需要SNI,而 java 7 只支持客户端的SNI,只 ...
- C#项目获取当前时间的农历时间
https://blog.csdn.net/cstester/article/details/7407044 using System.Globalization; class CnCanlendar ...
- spring cron表达式(定时器)
转: spring cron表达式(定时器) 写定时器时用到,记录一下: Cron表达式是一个字符串,字符串以5或6个空格隔开,分开工6或7个域,每一个域代表一个含义,Cron有如下两种语法 格式: ...