是否有更清晰的方法来处理不在聚合函数或 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.Aa.Ba.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.Aa.Ba.C 中的一个值,你不知道是哪一个。 事实上,MySQL 可能是唯一允许这种破坏逻辑的 dbms 系统。 @Eric 但我们知道是哪一个,因为我们知道TableAId 上是独一无二的。因此,仅返回任何 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 子句中并且包含非聚合

MySQL 聚合函数MySQL对GROUP BY的处理

org.apache.spark.sql.AnalysisException:表达式 't2.`sum_click_passed`' 既不在 group by 中,也不是聚合函数

查询没有重复和聚合函数或 GROUP BY 子句问题。 - 重复

mysql 可以group by 两个字段吗