了解窗口功能

窗口函数之前有SQL,窗口函数之后有SQL:这就是该工具的强大功能。 不幸的是,成为破坏交易者意味着很难掌握该功能。 本文旨在使它清晰易懂,以便您可以从今天开始使用它,并能够对此进行推理,并确定要使用窗口函数的情况

窗口函数背后的整个思想是允许您一次处理结果集的多个值:您可以在窗口中看到一些对等行,并能够从中计算出一个对等行,就像使用聚合函数时一样。

PostgreSQL具有许多功能,其中之一将对此处有很大的帮助,以便更好地了解窗口函数的功能。 我们在这里要做的第一步是了解功能可以访问哪些数据。 对于每个输入行,您都可以访问数据框架 ,而这里首先要了解的是该框架

首先,与array_agg会合 ,该函数将为您构建一个数组。 让我们使用此工具来了解窗框

 选择x,array_agg(x)结束(按x排序) 
从generate_series(1,3)作为t(x);

我们得到:

  x |  array_agg 
--- + -----------
1 | {1}
2 | {1,2}
3 | {1,2,3}
(3列)

上一个查询输出中的array_agg列使我们能够看到将要处理的窗口的完整确切内容。 此处的窗口定义over (order by x) ,实际上意味着over (order by x rows between unbounded preceding and current row) ,正如我们在以下查询中所看到的,其结果与之前相同:

 选择x, 
array_agg(x)over(按x排序
前无界之间的行
和当前行)
从generate_series(1,3)作为t(x);

也可以使用其他类型的框架规范 ,如以下示例所示:

 选择x, 
array_agg(x)over(在当前行之间行
和无限制的跟随)
从generate_series(1,3)作为t(x); x | array_agg
--- + -----------
1 | {1,2,3}
2 | {2,3}
3 | {3}
(3列)

如果根本不使用frame子句,则默认值为查看每行中的整个行集,如果要计算总和和百分比,这将非常有用,例如:

 选择x, 
在()上方将array_agg(x)作为框架,
sum(x)超过()作为总和,
x :: float / sum(x)作为()的一部分
从generate_series(1,3)作为t(x); x | 框架| 总和 部分
--- + --------- + ----- + -------------------
1 | {1,2,3} | 6 | 0.166666666666667
2 | {1,2,3} | 6 | 0.333333333333333
3 | {1,2,3} | 6 | 0.5
(3列)

您知道吗,您可以在一个SQL查询中同时计算列的总和和当前值相对于该总和的比例? 那就是我们现在正在谈论的窗口函数的突破。


在应用程序开发中掌握PostgreSQL可向应用程序开发人员教授高级SQL技术,用简单的查询替换成千上万的代码!

要了解有关诸如窗口函数,分组集之类的SQL gem的更多信息,请考虑一下我的书中的大量资源:“在应用程序开发中掌握PostgreSQL”。

购买任何电子版图书时,请使用优惠代码MEETUP15,并立即获得15%的折扣!


使用子句PARTITION BY可以定义其他框架。 要了解窗口划分的分区,最好将其与实际用例联系起来。 让我们使用公开可用的赛车数据的历史记录。

在Ergast数据库中,我们有一个结果表,其中包含所有已知种族的结果。 我们选择了在这篇文章首次发表之前发生的种族:

  -[记录1] --------------------------------------------- -------- 
种族| 890
年| 2013年
圆形 10
circuitid | 11
名称| 匈牙利大奖赛
日期| 2013-07-28
时间| 12:00:00
网址| http://en.wikipedia.org/wiki/2013_Hungarian_Grand_Prix

在那场比赛中,我们现在可以按位置顺序(首先是获胜者)获取竞争车手的列表,以及与来自相同构造函数的其他车手相比的排名:

 选择姓氏 
builders.name,
位置,
格式('%s /%s',
row_number()
过度(按constructorid划分
按位置排序,最后一个为null),结束计数(*)(按Constructorid划分)

如“ pos same constr”
从结果
使用驱动程序加入驱动程序
使用(constructorid)加入构造函数
其中raceid = 890
按位置排序;

partition bypartition by允许我们查看对等行 ,这里是结果表中的行,其中的constructorid与当前行相同。 在上一个SQL查询中,我们在format()调用中两次使用了该分区。 第一次使用row_number()窗口函数使我们相对于来自同一构造函数的其他驱动程序在竞争中的位置,第二次利用count(*)给出了来自同一构造函数的驱动程序中有多少驱动程序参与竞争:

 姓 名称| 位置|  pos同建设 
--------------- + ------------- + ---------- + --------- --------
汉密尔顿| 梅赛德斯| 1 | 1/2
莱科宁| 莲花F1 | 2 | 1/2
维特尔| 红牛| 3 | 1/2
韦伯| 红牛| 4 | 2/2
阿隆索| 法拉利| 5 | 1/2
格罗让| 莲花F1 | 6 | 2/2
纽扣 迈凯轮| 7 | 1/2
马萨| 法拉利| 8 | 2/2
佩雷斯| 迈凯轮| 9 | 2/2
马尔多纳多| 威廉姆斯| 10 | 1/2
赫尔肯贝格| 索伯| 11 | 1/2
韦尔涅| Toro Rosso | 12 | 1/2
里卡多| Toro Rosso | 13 | 2/2
范德加德| 卡特勒姆| 14 | 1/2
图片| 卡特勒姆| 15 | 2/2
边池| 马鲁西亚| 16 | 1/2
奇尔顿| 马鲁西亚| 17 | 2/2
di Resta | 印度力量| 18 | 1/2
罗斯伯格| 梅赛德斯| 19 | 2/2
Bottas | 威廉姆斯| ⦱| 2/2
苏蒂尔| 印度力量| ⦱| 2/2
古铁雷斯| 索伯| ⦱| 2/2
(22列)

未完成比赛的车手将获得NULL位置条目,为方便起见,我们的psql设置将其显示为⦱字符。

在单个SQL查询中,我们可以从竞赛中的每个驱动程序获取信息,并从整体上将其他信息添加到竞赛中。 请记住, 窗口函数仅在where子句之后发生,因此您只能从查询的可用结果集中查看行。


您已经知道的所有聚合函数都可以用于
窗口框架而不是分组子句,因此您已经可以使用
summinmaxcountavg和其他您习惯的。

您可能已经知道PostgreSQL可以使用
CREATE AGGREGATE命令来注册您自己的自定义聚合。 然后,可以给任何此类自定义聚合一个窗口框架定义,以使其也可以使用。

作为读者的练习,实施加权平均合计并将其用于至少有三列的表:日期,权重和度量,每天有几项度量。 现在,通过在分组子句或窗口框架中将自己的汇总应用于数据集来计算加权平均值

PostgreSQL当然包含在内置的聚合函数和许多内置的窗口函数中。

 选择姓氏 
位置,
row_number()
结束(按最快的lapspeed ::数字排序)
作为“最快”,
w上的ntile(3)作为“组”,
在w上滞后(surname,1)为“ previous”,
在w上领先(姓氏,1)为“ next”
从结果
使用驱动程序加入驱动程序
其中raceid = 890
窗口w as(按位置排序)
按位置排序;

在此示例中,您可以看到我们多次重复使用相同的窗口定义,因此为它命名以简化SQL。 在此查询中,我们将获取每个驾驶员在结果中的位置,以最快圈速为单位的位置,组号(如果使用ntile()函数将驾驶员分为4组ntile()的名称,由于lag()lead()函数,使之成为前一个驱动程序的驱动程序,并且驱动程序的名称紧挨当前驱动程序的名称:

 姓 位置| 最快的 组| 上一个| 下一个 
--------------- + ---------- + --------- + ------- + ----- ---------- + ---------------
汉密尔顿| 1 | 20 | 1 | ⦱| 莱科宁
莱科宁| 2 | 17 | 1 | 汉密尔顿| 维特尔
维特尔| 3 | 21 | 1 | 莱科宁| 韦伯
韦伯| 4 | 22 | 1 | 维特尔| 阿隆索
阿隆索| 5 | 15 | 1 | 韦伯| 格罗斯让
格罗斯让| 6 | 16 | 1 | 阿隆索| 纽扣
纽扣 7 | 12 | 1 | 格罗斯让| 马萨
马萨| 8 | 18 | 1 | 纽扣 佩雷斯
佩雷斯| 9 | 13 | 2 | 马萨| 马尔多纳多
马尔多纳多| 10 | 14 | 2 | 佩雷斯| 赫尔肯贝格
赫尔肯贝格| 11 | 9 | 2 | 马尔多纳多| 韦尔涅
韦尔涅| 12 | 11 | 2 | 赫尔肯贝格| 里卡多
里卡多| 13 | 8 | 2 | 韦尔涅| 范德加德
范德加| 14 | 6 | 2 | 里卡多| 图片
图片| 15 | 5 | 2 | 范德加| 比安奇
边池| 16 | 3 | 3 | 图片| 奇尔顿
奇尔顿| 17 | 4 | 3 | 边池| 迪雷斯塔
di Resta | 18 | 10 | 3 | 奇尔顿| 罗斯伯格
罗斯伯格| 19 | 19 | 3 | di Resta | 博塔斯
苏蒂尔| ⦱| 2 | 3 | 古铁雷斯| ⦱
古铁雷斯| ⦱| 1 | 3 | Bottas | 苏蒂尔
Bottas | ⦱| 7 | 3 | 罗斯伯格| 古铁雷斯
(22列)

我们可以看到, 最快的单圈速度并不像人们想象的那么重要,因为两个最快的车手甚至都没有完成比赛。 在SQL术语中,我们还看到我们可以从同一查询返回两个不同的顺序,并且再次可以戳其他行。


所谓的窗口函数的真正魔力实际上是它们在使用OVER ()子句及其PARTITION BYORDER BY以及frame子句时可以看到的数据帧。

您需要记住,在查询中始终将windowing子句视为最后一个,即在WHERE子句之后。 您只能在已选择要输出的任何行中查看:例如,无法直接计算不希望显示的值的百分比。 在这种情况下,您将需要使用子查询。

有关窗口函数用法的更具体的示例,您可以看到我的其他博客文章,例如“充分利用SQL”和“重置计数器”。

PostgresOpen会议,芝加哥,2014年:针对开发人员的PostgreSQL

如果您想了解更多SQL,请阅读我的《在应用程序开发中掌握PostgreSQL》和我的博客https://tapoueh.org。 玩弄SQL,用简单的方法解决复杂的问题!