zoukankan      html  css  js  c++  java
  • 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
    View Code

    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
    View Code

     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
    View Code

    这里的开窗函数“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
    View Code

     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

  • 相关阅读:
    POJ2454 Jersey Politics
    Codeforces 798D
    BZOJ4556 HEOI2016 字符串
    BZOJ1009 [HNOI2008]GT考试
    POJ3693 Maximum repetition substring
    POJ1226 Substrings
    POJ3450 Corporate Identity
    POJ3415 Common Substrings
    CSS Sprites(CSS精灵) 的优缺点
    window.location.replace
  • 原文地址:https://www.cnblogs.com/cnki/p/10269582.html
Copyright © 2011-2022 走看看