为啥不能在单个 SELECT 中混合聚合值和非聚合值?
Posted
技术标签:
【中文标题】为啥不能在单个 SELECT 中混合聚合值和非聚合值?【英文标题】:Why can't you mix Aggregate values and Non-Aggregate values in a single SELECT?为什么不能在单个 SELECT 中混合聚合值和非聚合值? 【发布时间】:2011-08-20 16:12:45 【问题描述】:我知道,如果您在 SELECT 语句中有一个聚合函数,那么语句中的所有其他值必须是聚合函数,或者列在 GROUP BY 子句中。我不明白为什么会这样。
如果我这样做:
SELECT Name, 'Jones' AS Surname FROM People
我明白了:
NAME SURNAME
Dave Jones
Susan Jones
Amy Jones
因此,DBMS 从每一行获取一个值,并在结果集中将一个值附加到它。没关系。但如果这行得通,我为什么不能这样做:
SELECT Name, COUNT(Name) AS Surname FROM People
这似乎是相同的想法,从每一行中获取一个值并附加一个值。但不是:
NAME SURNAME
Dave 3
Susan 3
Amy 3
我明白了:
您尝试执行的查询不包含指定表达式“ContactName”作为聚合函数的一部分。
我知道这是不允许的,但是这两种情况看起来如此相似,以至于我不明白为什么。是为了让 DBMS 更容易实现吗?如果有人能向我解释为什么它不像我认为的那样工作,我将非常感激。
【问题讨论】:
您认为count(Name)
在该查询中的含义是什么?
一些 RDBM 系统(如 SQL-Server、Postgres)具有“窗口”功能,可以显示类似于您所寻求的结果。当您显式或隐式使用GROUP BY
时(通过在SELECT
部分中使用聚合函数),您不能同时使用两者。
我也说看看OVER ()
子句中的PARTITION BY
,它们在查询中往往看起来更接近,事情可能会开始点击到位。我有时希望 SQL 不那么结构化:)
【参考方案1】:
聚合不适用于完整的结果,它们仅适用于结果中的组。
考虑一个包含以下内容的表格:
Person Pet
-------- --------
Amy Cat
Amy Dog
Amy Canary
Dave Dog
Susan Snake
Susan Spider
如果您使用对 Person 进行分组的查询,它会将数据分成以下组:
Amy:
Amy Cat
Amy Dog
Amy Canary
Dave:
Dave Dog
Susan:
Susan Snake
Susan Spider
如果您使用聚合,例如计数聚合,它将为每个组生成一个结果:
Amy:
Amy Cat
Amy Dog
Amy Canary count(*) = 3
Dave:
Dave Dog count(*) = 1
Susan:
Susan Snake
Susan Spider count(*) = 2
因此,查询 select Person, count(*) from People group by Person
为每个组提供了一条记录:
Amy 3
Dave 1
Susan 2
如果您也尝试在结果中获取 Pet 字段,那将不起作用,因为每个组中该字段可能有多个值。
(某些数据库,如 mysql,确实允许这样做,并且只返回组内的任何随机值,您有责任知道结果是否合理。)
如果您使用聚合,但未指定任何分组,则查询仍将分组,整个结果为单个组。因此查询select count(*) from Person
将创建一个包含所有记录的单个组,并且聚合可以计算该组中的记录。结果包含每组中的一行,并且由于只有一组,因此结果中将有一行。
【讨论】:
【参考方案2】:这样想:当您在没有分组的情况下调用 COUNT 时,它会将表“折叠”为单个组,从而无法在 select 子句中访问组中的各个项目。
您仍然可以使用子查询或交叉连接获得结果:
SELECT p1.Name, COUNT(p2.Name) AS Surname FROM People p1 CROSS JOIN People p2 GROUP BY p1.Name
SELECT Name, (SELECT COUNT(Name) FROM People) AS Surname FROM People
【讨论】:
我认为他希望子查询为(SELECT COUNT(p.Name) FROM People p WHERE p.Name = People.Name)
,而不是整数。
小心交叉连接;如果您将一个大表交叉连接到另一个大表,结果将是巨大的(它将 table1 x table2 中的行乘以)如果每个 CROSS JOIN 中有 10,000 行,则为您提供 100,000,000 行。我只对小型固定表使用 CROSS JOIN,通常是一年中的月份列表。
@ZeroK 幸运的是,RDBMS 足够聪明,可以优化执行计划。如果您查看 MS SQL 构建的第一条语句的实际执行计划,您将看到它在加入之前计算 COUNT 聚合。因此,您或 RDBMS 实际上不必处理两个行集的整个笛卡尔积。【参考方案3】:
正如其他人解释的那样,当您有 GROUP BY
或在 SELECT
列表中使用像 COUNT()
这样的聚合函数时,您正在对行进行分组,因此为每个组将匹配的行合并为一个。
当您只使用 SELECT
列表中的聚合函数,而没有使用 GROUP BY
时,将其视为您有一个 GROUP BY 1
,因此所有行都被分组,折叠成一个。因此,如果您有一百行,那么数据库就无法真正向您显示名称,因为它们有一百行。
但是,对于具有“窗口”功能的 RDBMS,您想要的是可行的。例如。使用没有GROUP BY
的聚合函数。
SQL-Server 示例,其中计算表中的所有行(名称):
SELECT Name
, COUNT(*) OVER() AS cnt
FROM People
以上是如何工作的?
它显示 Name
就像
COUNT(*) OVER() AS cnt
没有
存在并且
它显示 COUNT(*)
就像它正在对
表。
另一个例子。如果您在表格中有 Surname
字段,您可以使用类似这样的内容来显示按姓氏分组的所有行并计算有多少人拥有相同的姓氏:
SELECT Name
, Surname
, COUNT(*) OVER(PARTITION BY Surname) AS cnt
FROM People
【讨论】:
【参考方案4】:您的查询隐含地要求您的结果集中不同类型的行,这是不允许的。返回的所有行都应该是相同类型并且具有相同类型的列。
'SELECT name, surname' 想要为表中的每一行返回一行。
'SELECT COUNT(*)' 想要返回一个组合表中所有行的结果的单行。
我认为你是对的,在这种情况下,数据库可以合理地只执行两个查询,然后将“SELECT COUNT(*)”的结果复制到每个结果中。不这样做的一个原因是它会影响隐身性能:您实际上是在进行额外的自联接而不在任何地方声明它。
其他答案已经解释了如何编写这个查询的工作版本,所以我不会深入。
【讨论】:
+1。但是,COUNT(*) 想要返回单个值。 'Jones' 想要返回单个值。为什么一个允许,而另一个不允许?【参考方案5】:聚合函数和 group by 子句不是独立的事物,它们是同一事物的一部分,出现在查询的不同位置。如果您希望在列上进行聚合,则必须说明使用什么函数进行聚合;如果您希望拥有聚合功能,则必须将其应用于某些列。
【讨论】:
-1。这没有回答这个问题 - “为什么不能混合聚合值和非聚合值”。 它回答了上述问题,即为什么引擎不允许您在没有分组依据的情况下进行计数。你应该能够弄清楚其余的。它可能不彻底或不长,但这并不意味着它没有回答问题。 它确实回答了“为什么引擎不允许您在没有分组依据的情况下进行计数”的问题。这不是“为什么不能混合聚合值和非聚合值?”的问题。我不是说你错了——你显然是对的。但这不是我问题的答案。【参考方案6】:聚合函数从具有特定条件的多行中获取值,并将它们组合成一个值。此条件由您的语句中的GROUP BY
定义。所以你不能使用没有GROUP BY
的聚合函数
有
SELECT Name, 'Jones' AS Surname FROM People
您只需选择一个具有固定值的附加列...但使用
SELECT Name, COUNT(Name) AS Surname FROM People GROUP BY Name
您告诉 DBMS 选择名称,记住每个名称在表中出现的频率并将它们折叠成一行。因此,如果您省略 GROUP BY
,DBMS 无法判断,如何折叠记录
【讨论】:
以上是关于为啥不能在单个 SELECT 中混合聚合值和非聚合值?的主要内容,如果未能解决你的问题,请参考以下文章
为啥sql查询语句中的count(*)等聚合函数可以放在having后面,而不能放在where后面?
为啥 CROSS APPLY 与列和聚合函数需要 Group by