序言

 设计窗口函数目的?

  在开窗函数出现之前存在着很多用 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. [poj1160][IOI2000]Post Office【动态规划】

    传送门 https://vjudge.net/problem/POJ-1160#author=SCU2018 题目描述 在一条水平的公路上建有n个小屋,两个小屋间的距离是它们的横坐标之差的绝对值.保证 ...

  2. BUG关闭原因

    已解决:缺陷已经修复. 重复缺陷:是指在系统里相同原因的缺陷已经被其他人报告.在此缺陷被作为重复缺陷返回时,先不要立即取消.必须等到核查修复后,才在系统里取消.这是因为有些缺陷被误认为是重复缺陷,实际 ...

  3. 跨SQL注入

    概念 SQL Injection按照字面意思来翻译就是"SQL注射",常被叫做"SQL注入",它的含义就是利用某些数据库的外部接口把用户数据插入到实际数据库操作 ...

  4. 新建WINDOWS服务C#

    当前作业环境 Windows8.1 | Visual Studio 2013 一. 建立项目,选择"Windows服务"模板 二. 查看生成的项目,结构很像WinForm的项目,其 ...

  5. 理解Spark的核心RDD

    http://www.infoq.com/cn/articles/spark-core-rdd/

  6. luogu4602 混合果汁 (主席树)

    按照美味值从大到小排序,对于每个询问,我想二分找到一个前缀来满足条件 那么以单价为下标建主席树,维护区间的最大体积和 以及满足这个最大体积需要的价钱 然后二分答案,再在主席树上二分,找到恰好满足的那个 ...

  7. PWM实现ADC和DAC

    一.PWM实现AD 利用普通单片机的2个IO及一个运算放大器即可实现AD转换电路,而且很容易扩展成多通道.其占用资源少,成本低,AD 转换精度可以达到8位甚至更高,因此具有一定的实用价值. 1.1 硬 ...

  8. 20165223 2017-2018-2《Java程序设计》课程总结

    目录 每周作业链接汇总 实验报告链接汇总 自我剖析总结感悟 代码托管截图与链接 课堂项目实践 课后项目实践 课程收获与不足 学习建议 问卷调查 二维码链接 每周作业链接汇总 预备作业1:我期望的师生关 ...

  9. nodejs的某些api~(四)udp&dns

    今天记udp/数据报套接字和dns. udp UDP/数据报套接字 => require('dgram');dgram.createServer([type],[cb]);type:可以是'ud ...

  10. Linux服务器SSH免密互访

    1.编辑Hosts文件: [root@yqtrack-elk01 /]# vim /etc/hosts