是否有更清晰的方法来处理不在聚合函数或 GROUP BY 子句中的字段?
Posted
技术标签:
【中文标题】是否有更清晰的方法来处理不在聚合函数或 GROUP BY 子句中的字段?【英文标题】:Is there a clearer way to deal with fields that are not in aggregate functions or the GROUP BY clause? 【发布时间】:2019-09-16 09:56:10 【问题描述】:我经常遇到这样的查询:
SELECT
a.Id,
a.A,
a.B,
a.C,
SUM(b.Foo) AS foo
FROM
TableA AS a
JOIN TableB AS b
ON a.Id = b.TableAId
GROUP BY a.Id;
在 SQL Server 中(如果ONLY_FULL_GROUP_BY
为真,还有 mysql),这个查询不好。一切都必须是 a) 在聚合函数中,或 b) 在 GROUP BY
中。
我的问题是这两种解决方案看起来都很糟糕且具有误导性。如果你选择像MAX()
这样的随机聚合函数,你最终会得到:
SELECT
a.Id,
MAX(a.A) AS A,
MAX(a.B) AS B,
MAX(a.C) AS C,
SUM(b.Foo) AS foo
FROM
TableA AS a
JOIN TableB AS b
ON a.Id = b.TableAId
GROUP BY a.Id;
这个查询看起来像我们关心a.A
、a.B
和a.C
的最大值,并混淆了最大值没有意义的事实。
GROUP BY
好一点:
SELECT
a.Id,
a.A,
a.B,
a.C,
SUM(b.Foo) AS foo
FROM
TableA AS a
JOIN TableB AS b
ON a.Id = b.TableAId
GROUP BY a.Id, A, B, C;
但它仍然不是最佳的。在具有复杂分组的大型查询中,拥有所有这些额外字段会使阅读变得更加困难,我最初的印象是这里有一些实际上并不存在的额外分组层次结构。
我的背景主要是在关闭ONLY_FULL_GROUP_BY
的MySQL 中,所以我发现SQL Server 中的这个限制是不必要的。我希望两者之间有一些快乐的媒介;计算机查看此查询并看到不需要聚合 TableA
字段,而 TableB
(除了 TableAId
)中的任何字段都需要聚合,这似乎是一件简单的事情。
有什么想法吗?
【问题讨论】:
这并不傻。这是正确的逻辑。 MySQL 实现是一个坏的。 即使你的例子也有缺陷。你只被a.Id
分组。所以在这种情况下,MySQL 为你任意选择a.A
、a.B
、a.C
中的一个值,你不知道是哪一个。
事实上,MySQL 可能是唯一允许这种破坏逻辑的 dbms 系统。
@Eric 但我们知道是哪一个,因为我们知道TableA
在Id
上是独一无二的。因此,仅返回任何 ol'a.A
并没有任何问题——它们保证是相同的。
如果Id
是唯一的,那么GROUP BY a.Id, a.A, a.B, a.C
有什么问题?
【参考方案1】:
这不是 SQL Server 问题!您看到的行为是 MySQL 损坏(通常),这就是为什么现在的默认设置是禁止该行为。也就是说,SQL 标准允许通过表中的唯一列进行聚合并选择表中的其他列。但是,我认为只有 Postgres 实现了这一点。
这是您遗漏的一种方法:
SELECT a.*, b.foo
FROM TableA a JOIN
(SELECT b.TableAId, SUM(b.Foo) as foo
FROM TableB b
GROUP BY b.TableAId
) b
ON a.Id = b.TableAId;
【讨论】:
您的解决方案不会阻止索引工作吗?我想我不明白为什么会这样,但是在加入派生表时索引可能会很挑剔。 @JasonHamje 。 . .它不会阻止使用a(id)
上的索引。【参考方案2】:
MySQL 5.7 及更高版本可以很好地处理这个问题:
mysql [localhost:5724] msandbox (test) > select @@sql_mode;
+-------------------------------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-------------------------------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+-------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql [localhost:5724] msandbox (test) > SELECT
-> a.Id,
-> a.A,
-> a.B,
-> a.C,
-> SUM(b.Foo) AS foo
-> FROM
-> TableA AS a
-> JOIN TableB AS b
-> ON a.Id = b.TableAId
-> GROUP BY a.Id;
Empty set (0.01 sec)
看,没有错误!
因为这个查询是按TableA
的唯一键分组的,所以它可以告诉TableA
的其他列对您分组所依据的列有功能依赖。所以没有必要对模棱两可的结果提出错误。
选择列表中唯一对分组列没有函数依赖的列是b.Foo
,在此查询中它安全地位于聚合函数内。
所以 MySQL,虽然过去让开发人员知道如何编写避免歧义的查询,但现在有两个改进,都在 MySQL 5.7.5 (2014-09-25) 中实现:
-
ONLY_FULL_GROUP_BY 默认启用
MySQL 分析功能依赖关系,如果您的未聚合列在功能上依赖于分组列,则不会让开发人员感到烦恼。
这些改进已在 MySQL 中普遍使用 3.5 年(我写这篇文章时是 2019 年 4 月,而 5.7 于 2015 年 10 月发布)。 MySQL 需要多少年才能获得这项改进的功劳?
附:不知道还有哪些RDBMS产品能正确做函数依赖分析。
【讨论】:
以上是关于是否有更清晰的方法来处理不在聚合函数或 GROUP BY 子句中的字段?的主要内容,如果未能解决你的问题,请参考以下文章
HSQLDB:原因:使用 MAX,但不使用 Group By,并获取 java.sql.SQLSyntaxErrorException:表达式不在聚合或 GROUP BY 列中:
SQLSTATE [42000]:语法错误或访问冲突:1055 SELECT 列表的表达式 #3 不在 GROUP BY 子句中并且包含非聚合
org.apache.spark.sql.AnalysisException:表达式 't2.`sum_click_passed`' 既不在 group by 中,也不是聚合函数