开窗函数

开窗函数定义

一组行被称为一个窗口,开窗函数是指可以用于「分区」或「分组」计算的函数。这些函数结合 OVER 子句对组内的数据进行编号,并进行求和、计算平均值等统计。从这个角度来说,SUM、AVG 以及 ROW_NUMBER 等都可以称为开窗函数。

开窗函数可以分别应用于每个分区,把每个分区看成是一个窗口,并未每个分区进行计算。开窗函数必须放在 OVER 子句前边。

SQL Server 早古版本就提供了对开窗函数的支持,但 MySQL 直到 8.0 版本才开始支持,所以部分只使用老版本 MySQL 的工程师知识面会有欠缺。推荐使用 dbngin 管理多个数据库环境。

1. 将 OVER 子句与聚合函数结合使用

OVER ([PARTITION BY vale_expression, ...[n]])
  • PARTITION BY:将结果集划分为多个分区。开窗函数分别应用于每个分区,并为每个分区计算函数值。
  • vale_expression:指定对行集进行分区所依据的列,该列必须是 FROM 子句中生成的列,而且不能引用选择列表中的表达式或别名。

可以在单个查询中使用多个开窗函数,每个函数的 OVER 子句在分区和排序上可以不同。

例1

设有课程表 Course(Cno, CName, Credit, Semester)。查询全部课程的课程号、课程名、开课学期、学分以及该学期开设课程的总分、平均分、最低分、最高分

SELECT
    Cno,
    CName,
    Semester,
    Credit,
    SUM( credit ) OVER ( PARTITION BY Semester ) AS 'Total',
    AVG( credit ) OVER ( PARTITION BY Semester ) AS 'Avg',
    MIN( credit ) OVER ( PARTITION BY Semester ) AS 'Min',
    MAX( credit ) OVER ( PARTITION BY Semester ) AS 'Max'
FROM
    Course
例1数据查询结果

观察结果,最简单情况下,开窗函数可以理解为,将整张表的,分区聚合函数查询的结果,冗余到当前表的每一行记录中。

例2

订单订单明细表 OrderDetail

CREATE TABLE OrderDetail(
OrderID int NOT NULL, --- 订单号
ProductID int NOT NULL, --- 产品号
OrderQty int NOT NULL --- 订购数量
)

现在查询订单号、产品号、订购数量、每个订单的总订购数量以及每个产品的订购数量占该订单总订购数量的百分比,百分比保留到小数点后 2 位。

分析: 先按照订单号进行分区,然后对每个订单号进行分区,对分区中的订购数量进行求和即可。

SUM(OrderQty) OVER (PARTITION BY OrderID)

第二层查询要求统计每个产品订购数量占订单总订购数量的百分比,可以用产品数量除以订单总订购量得到,然后再进行一下小数点的转化即可。

SELECT
    OrderID 订单号,
    ProductID 产品号,
    OrderQty 订购数量,
    SUM( OrderQty ) OVER ( PARTITION BY OrderID ) AS 总计,
    CAST(
    1.0 * OrderQty / SUM( OrderQty ) OVER ( PARTITION BY OrderID ) * 100 AS DECIMAL ( 5, 2 )) AS 所占百分比 
FROM
    OrderDetail
例2数据查询结果

2. 将 OVER 子句与排名函数一起使用

排名函数为分区中的每一行返回一个排名值。更具所用函数不同,某些行可能与其他行具有相同的排名值(并列第一)。排名函数具有不确定性。

(1)RANK() 函数

RANK() OVER ([<partition_by_clause>, ...[n]] <order_by_clause>)
  • <partition_by_clause>:将 FROM 子句生成的结果集划分成排名函数适用的分区。
  • <order_by_clause>:分区中的排序依据列。

RANK() 函数每个分区的排名从 1 开始,如果排序时有排序值相同的行,则相同的行具有相同的排名,因此不一定返回连续的整数。

例3

用例 2 的 OrderDetail 表,查询订单号、产品号、订购数量以及每个产品在每个订单中的订购数量排名。

SELECT
    OrderID,
    OrderQty,
    ProductID,
    RANK() OVER ( PARTITION BY OrderID ORDER BY OrderQty DESC ) AS 'RANK'
FROM
    OrderDetail 
ORDER BY
    OrderID
例3数据查询结果

(2)DENSE_RANK() 函数

唯一的区别是排名中间没有任何间断,返回一个连续的整数。

(3)NTILE() 函数

作用是将有序分区中的行划分到指定数目的数组中,每个组有一个编号,编号从 1 开始。对于每一行,函数将返回此行所属的组的编号。

NTILE(integer_expression) OVER ([<partition_by_clause>] <order_by_clause>)

例4

用例 2 的 OrderDetail 表。将该表数据按订购数量降序排序,并将该表数据划分到 4 个组中。

SELECT
    OrderID,
    ProductID,
    OrderQty,
    NTILE( 4 ) OVER ( ORDER BY OrderQty DESC ) AS 'FourGroups' 
FROM
    OrderDetail

全部数据分为 4 组,因此不需要分区,直接在 OVER 子句中排序即可

例4数据查询结果

(4)ROW_NUMBER() 函数

返回结果集中每个分区内行的序列号,每个分区的第一行从 1 开始。

ROW_NUMBER() OVER ([<partition_by_clause>] <order_by_clause>)

例5

查询「电冰箱」类商品的商品名、销售单价以及该商品在该类商品中的价格排名。

自己实现

例6

查询 2022 年 1 月 1 日从每个供应商采购商品情况,列出商品号、商品名、供应商编号、进货数量,以及每个商品的进货数量在该供应商的所有商品的进货数量中的排名。

自己实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 一,开窗函数: 开窗函数:为将要被操作的行的集合定义一个窗口,它对一组值进行操作,不需要使用GROUP BY子句对...
    weiwei_js阅读 13,451评论 1 9
  • 一 . 开窗函数 分组函数 开窗函数 分组与开窗的区别分组函数每组只返回一行,而开窗函数每组返回多行。如下: 分组...
    文字抒意阅读 3,910评论 0 4
  • 1. 介绍 普通聚合函数聚合的行集是组,开窗函数聚合的行集是窗口。因此,普通聚合函数每组(Group by)只有一...
    Yobhel阅读 549评论 0 2
  • 开窗函数也属于分析函数,与聚合函数的不同之处是:对于每个组返回多行,而聚合函数对于每个组只返回一行。 格式:函数名...
    晓之以理的喵阅读 789评论 0 0
  • 1. 介绍 普通聚合函数聚合的行集是组,开窗函数聚合的行集是窗口。因此,普通聚合函数每组(Group by)只有一...
    幸运猪x阅读 8,126评论 0 4