什么时候可以将聚合函数嵌套在标准 SQL 中?
Posted
技术标签:
【中文标题】什么时候可以将聚合函数嵌套在标准 SQL 中?【英文标题】:When can aggregate functions be nested in standard SQL? 【发布时间】:2021-11-27 01:58:29 【问题描述】:我知道它在 SQL-92 中是不允许的。但从那以后它可能已经改变了,特别是当应用了一个窗口时。你能解释一下这些变化并给出引入它们的版本(或版本,如果有的话)?
示例
SUM(COUNT(votes.option_id)) OVER() 是否符合标准 SQL:2016(或更早版本)的有效语法?
这是我在Why can you nest aggregate functions when using a window function in PostgreSQL? 中的comment(未回答,在这么老的问题中可能不太可能)。
-
Codewars 上的 Calculating Running Total (SQL) kata 是其最受好评的解决方案(使用 PostgreSQL 13.0,一个高度符合标准的引擎,因此代码很可能是标准的):
SELECT
CREATED_AT::DATE AS DATE,
COUNT(CREATED_AT) AS COUNT,
SUM(COUNT(CREATED_AT)) OVER (ORDER BY CREATED_AT::DATE ROWS UNBOUNDED PRECEDING)::INT AS TOTAL
FROM
POSTS
GROUP BY
CREATED_AT::DATE
(可以简化为:
SELECT
created_at::DATE date,
COUNT(*) COUNT,
SUM(COUNT(*)) OVER (ORDER BY created_at::DATE)::INT total
FROM posts
GROUP BY created_at::DATE
我认为::
s 是一种我不知道的新语法。现在允许从 TIMESTAMP 转换为 DATE(在 SQL-92 中不允许)。)
-
正如this SO answer 解释的那样,即使没有窗口,Oracle 数据库也允许它从上下文中提取
GROUP BY
。我不知道标准是否允许。
【问题讨论】:
这是关于聚合(COUNT、SUM、MAX 等)和分析函数(COUNT、SUM、MAX OVER 等)。聚合(在您的示例中为COUNT(*)
)首先发生。然后,您可以在结果上应用分析函数(在您的示例中为SUM OVER
)。
Oracle 是个例外。在那里你可以嵌套两个聚合函数。这不符合标准,虽然我经常使用 Oracle,但我从不使用它。
::
是 PostgreSQL 中的转换运算符。它不符合标准。
@ThorstenKettner,您能否详细说明分析函数以及它们与聚合函数的区别?文档“信息技术 - 数据库语言 - SQL - 第 2 部分:基础(SQL/Foundation)”(我使用的是SQL:2011 late draftlinked from Wikipedia)甚至没有包含“分析”这个词。
我很抱歉造成混乱。 “分析函数”只是“窗口函数”的另一个词。在 SQL 标准中,他们只使用术语“窗口函数”。我仍然添加了一个答案来更详细地解释差异。
【参考方案1】:
您自己已经注意到了不同之处:一切都与窗户有关。例如,没有OVER
子句的COUNT(*)
是一个聚合函数。带有OVER
子句的COUNT(*)
是一个窗口函数。
通过使用聚合函数,您可以压缩在FROM
子句和WHERE
子句应用于GROUP BY
中的指定组或在没有GROUP BY
子句的情况下应用于一行的原始行。
之后应用窗口函数,也称为分析函数。它们不会更改结果行数,而只是通过查看所选数据的全部或部分行(窗口)来添加信息。
在
SELECT
options.id,
options.option_text,
COUNT(votes.option_id) as vote_count,
COUNT(votes.option_id) / SUM(COUNT(votes.option_id)) OVER() * 100.0 as vote_percentage
FROM options
LEFT JOIN votes on options.id = votes.option_id
GROUP BY options.id;
我们首先将投票加入选项,然后通过将连接的行聚合到每个选项的一个结果行来计算每个选项的投票 (GROUP BY options.id
)。我们依靠投票表中的一个不可为空的列 (COUNT(votes.option_id)
,因此在没有投票的情况下我们得到零计数,因为在外部连接行中,该列设置为 null。
在聚合所有行并因此每个选项获得一行之后,我们在此结果集上应用一个窗口函数 (SUM() OVER
)。我们将分析SUM
应用于投票计数(SUM(COUNT(votes.option_id))
通过查看整个结果集(空的OVER
子句),从而在每一行中获得相同的总投票计数。我们使用这个值进行计算:option's票数除以总票数乘以 100,即选项占总票数的百分比。
PostgreSQL 查询非常相似。我们选择每个日期的帖子数(COUNT(created_at)
只不过是一个COUNT(*)
)以及这些计数的运行总数(通过使用查看当前行之前的所有行的窗口)。
因此,虽然这看起来我们嵌套了两个聚合函数,但事实并非如此,因为SUM OVER
不被视为聚合函数,而是分析/窗口函数。
Oracle 确实允许直接在另一个上应用聚合函数,从而在以前的聚合上调用最终聚合。这允许我们获得一个结果行,例如总和的平均值,而无需为此编写子查询。然而,这不符合 SQL 标准,甚至在当时的 Oracle 开发人员中也非常不受欢迎。
【讨论】:
很好的答案,谢谢!在我接受之前,关于您的查询,我有以下问题:options.option_text
应该如何被选中?我认为您不能选择涉及非分组列的表达式,除非它位于聚合函数下。
不,SQL 标准还允许在功能上依赖于按列分组的列。 options.id
可能是表的主键。因此options.option_text
的功能依赖于它,因为有一个options.id
,您会找到一个options
行,而在该行中恰好有一个options.option_text
。有些 DBMS 支持函数依赖检测,有些则不支持。
啊,是的,新版本确实可以!而且,尽管没有说明,但正如您在评论中暗示的那样,列名表明它可能存在足够的功能依赖性。 (而且我不知道几年前仅在文档中设置约束的做法是否仍被广泛遵循,否定了此功能。但希望不会,特别是考虑到至少自 SQL:2011 以来NOT ENFORCED
的可用性。)
以上是关于什么时候可以将聚合函数嵌套在标准 SQL 中?的主要内容,如果未能解决你的问题,请参考以下文章