大家好!我是只谈技术不剪发的 Tony 老师。今天给大家分享 MysqL 8.0 中的一个新特性:窗口函数。
许多关系型数据库,例如 Oracle、sql Server、Postgresql 以及 sqlite 等,都实现了 sql 标准定义的窗口函数;MysqL 8.0 终于也增加了这个功能,今天我们就来详细介绍一下 MysqL 中的窗口函数。
这里是一份 SQL 窗口函数速查表,可以方便我们快速回顾和查找相关信息。
窗口函数概述
窗口函数(Window Function)针对查询中的每一行数据,基于和它相关的一组数据计算出一个结果。窗口函数在某些数据库中也叫做分析函数(Analytic Function)。
为了便于理解,我们可以比较一下聚合函数和窗口函数的区别。首先创建一个销售数据示例表 sales:
CREATE TABLE sales
(
year INT,
country VARCHAR(20),
product VARCHAR(32),
profit INT
);
insert into sales(year, country, product, profit)
values
(2000, 'Finland', 'Computer', 1500),
(2001, 'USA', 1200), 'Phone', 10),
(2000, 'India', 'Calculator', 75), 'TV', 150), 100), 50), 100);
MysqL> SELECT SUM(profit) AS total_profit FROM sales;
+--------------+
| total_profit |
+--------------+
| 7535 |
+--------------+
1 row in set (0.00 sec)
SELECT year, profit,
SUM(profit) OVER() AS total_profit
FROM sales;
MysqL> SELECT year,
-> SUM(profit) OVER() AS total_profit
-> FROM sales;
+------+---------+------------+--------+--------------+
| year | country | product | profit | total_profit |
+------+---------+------------+--------+--------------+
| 2000 | Finland | Computer | 1500 | 7535 |
| 2001 | USA | Computer | 1200 | 7535 |
| 2001 | Finland | Phone | 10 | 7535 |
| 2000 | India | Calculator | 75 | 7535 |
| 2001 | USA | TV | 150 | 7535 |
| 2000 | India | Computer | 1200 | 7535 |
| 2000 | USA | Calculator | 75 | 7535 |
| 2000 | USA | Computer | 1500 | 7535 |
| 2000 | Finland | Phone | 100 | 7535 |
| 2001 | USA | Calculator | 50 | 7535 |
| 2001 | USA | Computer | 1500 | 7535 |
| 2000 | India | Calculator | 75 | 7535 |
| 2001 | USA | TV | 100 | 7535 |
+------+---------+------------+--------+--------------+
13 rows in set (0.00 sec)
从查询的结果可以看出,窗口函数 SUM(profit) OVER() 执行了和聚合函数 SUM(profit) 类似的汇总。不过聚合函数只返回了一个汇总之后的结果,而窗口函数为每一行数据都返回了结果。
窗口函数只能出现在 SELECT 列表和 ORDER BY 子句中,查询语句的处理顺序依次为 FROM、WHERE、GROUP BY、聚合函数、HAVING、窗口函数、SELECT disTINCT、ORDER BY、LIMIT。
窗口函数与其他函数的语法区别主要在于 OVER 子句,接下来我们介绍它的语法。
窗口函数语法
窗口函数的语法如下:
window_function(expr)
OVER (
PARTITION BY ...
ORDER BY ...
frame_clause
)
OVER 子句包含三个可选项:分区(PARTITION BY)、排序(ORDER BY)以及窗口大小(frame_clause)。
PARTITION BY
PARTITION BY 选项用于将数据行拆分成多个分区(组),窗口函数基于每一行数据所在的组进行计算并返回结果。如果省略了 PARTITION BY,所有的数据作为一个组进行计算。在上面的示例中,SUM(profit) OVER() 就是将所有的销售数据看作一个分区。
下面的查询按照不同的国家汇总销量:
MysqL> SELECT year,
-> SUM(profit) OVER(PARTITION BY country) AS country_profit
-> FROM sales;
+------+---------+------------+--------+----------------+
| year | country | product | profit | country_profit |
+------+---------+------------+--------+----------------+
| 2000 | Finland | Computer | 1500 | 1610 |
| 2001 | Finland | Phone | 10 | 1610 |
| 2000 | Finland | Phone | 100 | 1610 |
| 2000 | India | Calculator | 75 | 1350 |
| 2000 | India | Computer | 1200 | 1350 |
| 2000 | India | Calculator | 75 | 1350 |
| 2001 | USA | Computer | 1200 | 4575 |
| 2001 | USA | TV | 150 | 4575 |
| 2000 | USA | Calculator | 75 | 4575 |
| 2000 | USA | Computer | 1500 | 4575 |
| 2001 | USA | Calculator | 50 | 4575 |
| 2001 | USA | Computer | 1500 | 4575 |
| 2001 | USA | TV | 100 | 4575 |
+------+---------+------------+--------+----------------+
13 rows in set (0.00 sec)
sql 标准要求 PARTITION BY 之后只能使用字段名,不过 MysqL 允许指定表达式。例如,假设表中存在一个名为 ts 的 TIMESTAMP 类型字段,sql 表中允许 PARTITION BY ts 但是不允许 PARTITION BY HOUR(ts);而 MysqL 两者都允许。
另外,我们也可以指定多个分组字段:
PARTITION BY expr [, expr] ...
ORDER BY
ORDER BY 选项用于指定分区内数据的排序,排序字段数据相同的行是对等行(peer)。如果省略 ORDER BY ,分区内的数据不进行排序,不按照固定顺序处理, 而且所有数据都是对等行。ORDER BY 通常用于排名分析函数,参考下文中的专用窗口函数。
以下查询按照国家进行分组,按照年份和产品名称进行排序,然后汇总销量:
MysqL> SELECT year,
-> SUM(profit) OVER(PARTITION BY country ORDER BY year, product) AS country_profit
-> FROM sales;
+------+---------+------------+--------+----------------+
| year | country | product | profit | country_profit |
+------+---------+------------+--------+----------------+
| 2000 | Finland | Computer | 1500 | 1500 |
| 2000 | Finland | Phone | 100 | 1600 |
| 2001 | Finland | Phone | 10 | 1610 |
| 2000 | India | Calculator | 75 | 150 |
| 2000 | India | Calculator | 75 | 150 |
| 2000 | India | Computer | 1200 | 1350 |
| 2000 | USA | Calculator | 75 | 75 |
| 2000 | USA | Computer | 1500 | 1575 |
| 2001 | USA | Calculator | 50 | 1625 |
| 2001 | USA | Computer | 1200 | 4325 |
| 2001 | USA | Computer | 1500 | 4325 |
| 2001 | USA | TV | 150 | 4575 |
| 2001 | USA | TV | 100 | 4575 |
+------+---------+------------+--------+----------------+
13 rows in set (0.00 sec)
ORDER BY 之后的表达式也可以使用 ASC 或者 DESC 指定排序方式,默认为 ASC。对应升序排序,NULL 排在最前面;对于降序排序,NULL 排在最后面:
ORDER BY expr [ASC|DESC] [, expr [ASC|DESC]] ...
OVER 子句中的 ORDER BY 选项只用于分区内的数据排序;如果想要对最终的结果进行排序,可以使用 ORDER BY 子句。
窗口选项
frame_clause 选项用于在当前分区中指定一个数据窗口,也就是一个与当前行相关的数据子集。窗口会随着当前处理的数据行而移动,例如:
frame_clause 的语法如下:
{ROWS | RANGE} frame_start
{ROWS | RANGE} BETWEEN frame_start AND frame_end
其中,
- ROWS 表示通过开始行和结束行的位置指定窗口,偏移量以行数为单位进行计算。
- RANGE 表示通过开始行和结束行的数值指定窗口,偏移量以数值为单位进行计算。
frame_start 和 frame_end 分别表示窗口的开始行和结束行。如果只有 frame_start,默认以当前行作为窗口的结束。
CURRENT ROW
UNBOUNDED PRECEDING
UNBOUNDED FOLLOWING
expr PRECEDING
expr FOLLOWING
如果同时指定了两者,frame_start 不能晚于 frame_end,例如 BETWEEN 1 FOLLOWING AND 1 PRECEDING 就是一个无效的窗口。
frame_start 和 frame_end 的具体意义如下:
- CURRENT ROW:对于 ROWS 方式,代表了当前行;对于 RANGE,代表了当前行的所有对等行。
- UNBOUNDED PRECEDING:代表了分区中的第一行。
- UNBOUNDED FOLLOWING:代表了分区中的最后一行。
- expr PRECEDING:对于 ROWS 方式,代表了当前行之前的第 expr 行;对于 RANGE,代表了等于当前行的值减去 expr 的所有行;如果当前行的值为 NULL,代表了当前行的所有对等行。
- expr FOLLOWING:对于 ROWS 方式,代表了当前行之后的第 expr 行;对于 RANGE,代表了等于当前行的值加上 expr 的所有行;如果当前行的值为 NULL,代表了当前行的所有对等行。
对于 expr PRECEDING 和 expr FOLLOWING),expr 可以是一个 ? 参数占位符(用于预编译语句)、非负的数值常量或者 INTERVAL val unit 格式的时间间隔。对于 INTERVAL 表达式,val 是一个非负的时间间隔值,unit 是一个时间间隔单位,例如 INTERVAL 1 DAY。关于时间间隔常量的详细信息,可以参考官方文档中的 DATE_ADD() 函数介绍。
基于数字或者时间间隔的 RANGE 窗口需要指定一个基于数字或者时间间隔的 ORDER BY。以下是一些有效的 expr PRECEDING 和 expr FOLLOWING 窗口:
10 PRECEDING
INTERVAL 5 DAY PRECEDING
5 FOLLOWING
INTERVAL '2:30' MINUTE_SECOND FOLLOWING
以下查询演示了如何实现累计求和以及移动平均值:
MysqL> SELECT year, product ROWS UNBOUNDED PRECEDING) AS running_total,
-> AVG(profit) OVER(PARTITION BY country ORDER BY year, product ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS running_average
-> FROM sales;
+------+---------+------------+--------+---------------+-----------------+
| year | country | product | profit | running_total | running_average |
+------+---------+------------+--------+---------------+-----------------+
| 2000 | Finland | Computer | 1500 | 1500 | 800.0000 |
| 2000 | Finland | Phone | 100 | 1600 | 536.6667 |
| 2001 | Finland | Phone | 10 | 1610 | 55.0000 |
| 2000 | India | Calculator | 75 | 75 | 75.0000 |
| 2000 | India | Calculator | 75 | 150 | 450.0000 |
| 2000 | India | Computer | 1200 | 1350 | 637.5000 |
| 2000 | USA | Calculator | 75 | 75 | 787.5000 |
| 2000 | USA | Computer | 1500 | 1575 | 541.6667 |
| 2001 | USA | Calculator | 50 | 1625 | 916.6667 |
| 2001 | USA | Computer | 1200 | 2825 | 916.6667 |
| 2001 | USA | Computer | 1500 | 4325 | 950.0000 |
| 2001 | USA | TV | 150 | 4475 | 583.3333 |
| 2001 | USA | TV | 100 | 4575 | 125.0000 |
+------+---------+------------+--------+---------------+-----------------+
13 rows in set (0.01 sec)
对于 running_average,每个分区内的第一行数据没有 PRECEDING,结果是当前行和下一行的平均值;最后一行数据没有 FOLLOWING,结果是当前行和上一行的平均值。
另外,这篇文章介绍了如何基于时间间隔指定窗口大小,从而实现银行等金融机构可疑支付交易的监测。
如果没有指定 frame_clause,默认的窗口取决于是否存在 ORDER BY 选项:
- 如果指定了 ORDER BY:默认窗口从分区的开始直到当前行,包括当前行的所有对等行。此时默认窗口相当于:
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
; - 如果没有指定 ORDER BY:默认窗口就是整个分区,因为此时所有的数据都是对等行。此时默认窗口相当于:
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
。
由于默认窗口取决于是否包含了 ORDER BY 选项,而增加 ORDER BY 获得确定结果的同时又可能改变结果(例如 SUM() 函数的结果就会因此而改变)。所以,为了获得相同的结果,但又按照 ORDER BY 排序,可以总是明确指定一个窗口选项。
如果当前行的数据为 NULL,窗口的定义可能不太明确。以下示例说明了这种情况下各种窗口选项的作用:
-
ORDER BY X ASC RANGE BETWEEN 10 FOLLOWING AND 15 FOLLOWING
,窗口从 NULL 开始到 NULL 结束,也就是说窗口只包含 NULL 数据。 -
ORDER BY X ASC RANGE BETWEEN 10 FOLLOWING AND UNBOUNDED FOLLOWING
,窗口从 NULL 开始到分区的最后结束。由于 ASC 排序时 NULL 值出现在最前面,因此窗口就是整个分区。 -
ORDER BY X DESC RANGE BETWEEN 10 FOLLOWING AND UNBOUNDED FOLLOWING
,窗口从 NULL 开始到分区的最后结束。由于 DESC 排序时 NULL 值出现在最后面,因此窗口只包含 NULL 数据。 -
ORDER BY X ASC RANGE BETWEEN 10 PRECEDING AND UNBOUNDED FOLLOWING
,窗口从 NULL 开始到分区的最后结束。由于 ASC 排序时 NULL 值出现在最前面,因此窗口就是整个分区。 -
ORDER BY X ASC RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING
,窗口从 NULL 开始到 NULL 结束,也就是说窗口只包含 NULL 数据。 -
ORDER BY X ASC RANGE BETWEEN 10 PRECEDING AND 1 PRECEDING
,窗口从 NULL 开始到 NULL 结束,也就是说窗口只包含 NULL 数据。 -
ORDER BY X ASC RANGE BETWEEN UNBOUNDED PRECEDING AND 10 FOLLOWING
,窗口从分区的第一行开始到 NULL 结束。由于 ASC 排序时 NULL 值出现在最前面,因此窗口只包含 NULL 数据。
命名窗口
OVER 子句除了直接定义各种选项之外,还可以使用一个预定义的窗口变量。窗口变量使用 WINDOW 子句进行定义,语法上位于 HAVING 和 ORDER BY 之间。
window_function(expr) OVER window_name
WINDOW window_name AS (PARTITION BY ... ORDER BY ... frame_clause)
WINDOW window_name AS (other_window_name)
如果查询中多个窗口函数的 OVER 子句相同,利用 WINDOW 子句定义一个窗口变量,然后在多个 OVER 子句中使用该变量可以简化查询语句:
SELECT year,
SUM(profit) OVER w AS country_total,
AVG(profit) OVER w AS country_average,
COUNT(profit) OVER w AS country_count
FROM sales
WINDOW w AS (PARTITION BY country);
OVER 子句还可以使用 OVER (window_name …) 基于已有的窗口变量进行修改,但是只能增加其他选项。例如,以下查询定义了一个窗口变量,指定了分区选项;然后在 OVER 子句中增加了不同的 ORDER BY 选项:
SELECT year,
SUM(profit) OVER (w ORDER BY year,
AVG(profit) OVER (w ORDER BY year, product ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS running_average,
COUNT(profit) OVER w AS country_count
FROM sales
WINDOW w AS (PARTITION BY country);
OVER 子句只能为窗口变量增加选项,而不能修改原来的选项。以下是一个错误的示例:
OVER (w PARTITION BY year)
... WINDOW w AS (PARTITION BY country)
另外,窗口变量的定义中也可以使用其他窗口变量。例如:
WINDOW w1 AS (w2), w2 AS (), w3 AS (w1)
w1 反向引用了 w2,w3 正向引用了 w1。但是注意不要产生循环引用,例如以下定义将会产生错误:
WINDOW w1 AS (w2), w2 AS (w3), w3 AS (w1)
w1、w2、w3 之间是一个循环引用,无法真正定义窗口变量。
窗口函数列表
MysqL 中的窗口函数可以分为两类:聚合窗口函数和专用窗口函数。
聚合窗口函数
许多 MysqL 聚合函数都支持 OVER 子句,从而作为窗口函数使用:
- AVG(),计算平均值;
- BIT_AND(),按位与运算;
- BIT_OR(),按位或运算;
- BIT_XOR(),按位异或运算;
- COUNT(),计算行数;
- JSON_ARRAYAGG(),以 JSON 数组返回数据;
- JSON_OBJECTAGG(),以 JSON 对象返回数据;
- MAX(),计算最大值;
- MIN(),计算最小值;
- STDDEV_POP()、STDDEV()、STD(),计算总体标准差;
- STDDEV_SAMP(),计算样本标准差;
- SUM(),计算和值;
- VAR_POP()、VARIANCE(),计算总体方差;
- VAR_SAMP(),计算样本方差。
聚合窗口函数基于当前行所在的窗口进行计算,窗口的定义和默认值可以参考上文中的窗口选项。关于这些聚合窗口函数的具体介绍,可以参考官方文档。
专用窗口函数
- CUME_disT(),返回累积分布值,也就是分区内小于等于当前行的数据行占比。取值范围 [0,1];
- DENSE_RANK(),返回当前行在分区内的排名,数据相同的行排名相同,随后的排名不会产生跳跃;
- FirsT_VALUE(),返回当前窗口内的第一行;
- LAG(),返回分区内当前行上面(之前)的第 N 行;
- LAST_VALUE(),返回当前窗口内的最后一行;
- LEAD(),返回分区内当前行下面(之后)的第 N 行;
- NTH_VALUE(),返回当前窗口内的第 N 行;
- NTILE(),将当前分区拆分成 N 个组,返回当前行所在的组编号;
- PERCENT_RANK(),返回分区内排除最高值之后,小于当前行的数据行占比,计算方式为 (rank - 1) / (rows - 1);
- RANK(),返回当前行在分区内的排名,数据相同的行排名相同,随后的排名会产生跳跃;
- ROW_NUMBER(),返回当前行在分区内的编号,从 1 开始。
以下窗口函数基于当前行所在的窗口进行计算:
FirsT_VALUE()
LAST_VALUE()
NTH_VALUE()
以下函数基于整个分区进行计算:
CUME_disT()
DENSE_RANK()
LAG()
LEAD()
NTILE()
PERCENT_RANK()
RANK()
ROW_NUMBER()
sql 标准不允许这些函数指定窗口选项,MysqL 允许为它们指定窗口选项,但是会忽略这些选项。
以下是一些用于排名的窗口函数示例:
MysqL> SELECT year,
-> ROW_NUMBER() OVER(PARTITION BY country ORDER BY profit DESC) AS "row_number",
-> RANK() OVER(PARTITION BY country ORDER BY profit DESC) AS "rank",
-> DENSE_RANK() OVER(PARTITION BY country ORDER BY profit DESC) AS "dense_rank",
-> PERCENT_RANK() OVER(PARTITION BY country ORDER BY profit DESC) AS "percent_rank"
-> FROM sales;
+------+---------+------------+--------+------------+------+------------+--------------------+
| year | country | product | profit | row_number | rank | dense_rank | percent_rank |
+------+---------+------------+--------+------------+------+------------+--------------------+
| 2000 | Finland | Computer | 1500 | 1 | 1 | 1 | 0 |
| 2000 | Finland | Phone | 100 | 2 | 2 | 2 | 0.5 |
| 2001 | Finland | Phone | 10 | 3 | 3 | 3 | 1 |
| 2000 | India | Computer | 1200 | 1 | 1 | 1 | 0 |
| 2000 | India | Calculator | 75 | 2 | 2 | 2 | 0.5 |
| 2000 | India | Calculator | 75 | 3 | 2 | 2 | 0.5 |
| 2000 | USA | Computer | 1500 | 1 | 1 | 1 | 0 |
| 2001 | USA | Computer | 1500 | 2 | 1 | 1 | 0 |
| 2001 | USA | Computer | 1200 | 3 | 3 | 2 | 0.3333333333333333 |
| 2001 | USA | TV | 150 | 4 | 4 | 3 | 0.5 |
| 2001 | USA | TV | 100 | 5 | 5 | 4 | 0.6666666666666666 |
| 2000 | USA | Calculator | 75 | 6 | 6 | 5 | 0.8333333333333334 |
| 2001 | USA | Calculator | 50 | 7 | 7 | 6 | 1 |
+------+---------+------------+--------+------------+------+------------+--------------------+
13 rows in set (0.00 sec)
注意不同函数的排名方式。
MysqL> SELECT year,
-> LEAD(profit, 1, 0) OVER(PARTITION BY country ORDER BY profit DESC) AS "lead",
-> LAG(profit, 0) OVER(PARTITION BY country ORDER BY profit DESC) AS "lag",
-> FirsT_VALUE(profit) OVER(PARTITION BY country ORDER BY profit DESC) AS "first_value",
-> LAST_VALUE(profit) OVER(PARTITION BY country ORDER BY profit DESC) AS "last_value",
-> NTH_VALUE(profit, 2) OVER(PARTITION BY country ORDER BY profit DESC) AS "nth_value"
-> FROM sales;
+------+---------+------------+--------+------+------+-------------+------------+-----------+
| year | country | product | profit | lead | lag | first_value | last_value | nth_value |
+------+---------+------------+--------+------+------+-------------+------------+-----------+
| 2000 | Finland | Computer | 1500 | 100 | 0 | 1500 | 1500 | NULL |
| 2000 | Finland | Phone | 100 | 10 | 1500 | 1500 | 100 | 100 |
| 2001 | Finland | Phone | 10 | 0 | 100 | 1500 | 10 | 100 |
| 2000 | India | Computer | 1200 | 75 | 0 | 1200 | 1200 | NULL |
| 2000 | India | Calculator | 75 | 75 | 1200 | 1200 | 75 | 75 |
| 2000 | India | Calculator | 75 | 0 | 75 | 1200 | 75 | 75 |
| 2000 | USA | Computer | 1500 | 1500 | 0 | 1500 | 1500 | 1500 |
| 2001 | USA | Computer | 1500 | 1200 | 1500 | 1500 | 1500 | 1500 |
| 2001 | USA | Computer | 1200 | 150 | 1500 | 1500 | 1200 | 1500 |
| 2001 | USA | TV | 150 | 100 | 1200 | 1500 | 150 | 1500 |
| 2001 | USA | TV | 100 | 75 | 150 | 1500 | 100 | 1500 |
| 2000 | USA | Calculator | 75 | 50 | 100 | 1500 | 75 | 1500 |
| 2001 | USA | Calculator | 50 | 0 | 75 | 1500 | 50 | 1500 |
+------+---------+------------+--------+------+------+-------------+------------+-----------+
13 rows in set (0.00 sec)
窗口函数限制
sql 标准不允许在 UPDATE 或者 DELETE 语句中使用窗口函数更新数据,但是允许在子查询中使用这些函数。
MysqL 解析器可以接收以下标准 sql 选项但是不会生效:
- 以 GROUPS 为单位的窗口选项会产生错误,目前只支持 ROWS 和 RANGE 方式;
- 指定窗口时的 EXCLUDE 选项会产生错误;
- 使用 IGnorE NULLS 选项会产生错误,目前只支持 RESPECT NULLS;
- 使用 FROM LAST 选项会产生错误,目前只支持 FROM FirsT。
窗口函数优化
窗口函数会对优化器产生以下影响:
- 如果子查询中包含窗口函数,就不会使用派生表合并优化。此时子查询只会使用物化进行优化。
- 半连接不能用于窗口函数优化,因为半连接只能用于 WHERE 和 JOIN … ON 子查询,它们不能包含窗口函数。
- 优化器按照顺序处理多个具有相同排序规则的窗口,因此除了第一个窗口,其他都可以省略排序操作。
- 优化器不会尝试合并多个原本可以一次获取的窗口,例如多个定义完全相同的 OVER 子句。解决办法就是通过 WINDOW 子句定义一个窗口变量,然后在多个 OVER 子句中进行引用。
聚合函数不作为窗口函数使用时,会在尽可能的最外层查询执行聚合操作。例如,以下查询中,MysqL 知道 COUNT(t1.b) 不是外部查询中的聚合函数,因为它属于 WHERE 子句:
SELECT * FROM t1 WHERE t1.a = (SELECT COUNT(t1.b) FROM t2);
所以,MysqL 在子查询中进行聚合操作,将 t1.b 看作一个常量并且返回 t2 中的数据行数。
如果将 WHERE 替换成 HAVING 将会导致错误:
MysqL> SELECT * FROM t1 HAVING t1.a = (SELECT COUNT(t1.b) FROM t2);
ERROR 1140 (42000): In aggregated query without GROUP BY, expression #1
of SELECT list contains nonaggregated column 'test.t1.a'; this is
incompatible with sql_mode=only_full_group_by
错误的原因在于 COUNT(t1.b) 可以出现在 HAVING 中,从而导致外部查询产生聚合操作。
窗口函数(包括聚合窗口函数)的处理没有那么复杂,它们总是在当前子查询中进行聚合操作。
窗口函数的计算可能会受到系统变量 windowing_use_high_precision 的影响,该变量决定了计算窗口的操作是否确保不会丢失精度。默认启用了 windowing_use_high_precision。
对于某些移动窗口的聚合操作,可以使用反向聚合功能删除不需要的数据。这种方法可以提高性能,但可能导致精度丢失。例如,将一个很小的浮点数和一个很大的值相加,会导致这个浮点数被“遮盖”;随后对这个大值进行反向取值时,小浮点数的效果就会丢失。
反向聚合只会导致浮点数据类型的精度丢失;对于其他类型不会有影响,包括 DECIMAL 数据类型。
- 对于浮点型数据,反向聚合可能导致精度丢失。默认设置可以避免反向聚合,以性能的牺牲确保了精度。如果可以丢失一定的精度,可以禁用 windowing_use_high_precision 以支持反向聚合。
- 对于非浮点型的数据类型,反向聚合不会产生问题,因此无论 windowing_use_high_precision 如何设置都会使用反向聚合。
- windowing_use_high_precision 对于 MIN() 和 MAX() 聚合函数没有影响,因为它们永远不会使用反向聚合。
对于计算方差的 STDDEV_POP()、STDDEV_SAMP()、VAR_POP()、VAR_SAMP() 等聚合函数,可以使用优化模式或者默认模式。 优化模式可能导致最后一位小数略有不同,如果允许存在这些偏差,经用 windowing_use_high_precision 可以利用优化模式。
对于 EXPLAIN 命令,传统输出格式显示窗口执行信息过于复杂;如果想要显示相关信息,可以使用 EXPLAIN FORMAT=JSON,然后查找 windowing 元素。
如果你点击了收藏⭐,请不要忘了关注❤️、评论