【问题标题】:MySQL - Selecting a Column not in Group ByMySQL - 选择不在分组依据中的列
【发布时间】:2010-11-04 15:11:18
【问题描述】:

我正在尝试向预先存在的应用程序添加功能,我遇到了一个类似这样的 MySQL 视图:

SELECT
     AVG(table_name.col1),
     AVG(table_name.col2),
     AVG(table_name.col3),
     table_name.personID,
     table_name.col4
FROM table_name
GROUP BY table_name.personID;

好的,所以有一些聚合函数。您可以选择 personID,因为您正在按它进行分组。但它也会选择不在聚合函数中且不属于 GROUP BY 子句的列。这怎么可能???它只是选择一个随机值,因为每个组的值肯定不是唯一的吗?

我来自哪里(MSSQL Server),这是一个错误。有人可以向我解释这种行为以及为什么它在 MySQL 中是允许的吗?

【问题讨论】:

    标签: mysql group-by


    【解决方案1】:

    我应该再用 Google 搜索一下……看来我找到了my answer

    MySQL 扩展了 GROUP BY 的使用,所以 您可以使用非聚合列 或 SELECT 列表中的计算 未出现在 GROUP BY 中的 条款。您可以使用此功能来 通过避免获得更好的性能 不必要的列排序和 分组。例如,您不需要 在 customer.name 上分组 以下查询

    在标准 SQL 中,您必须添加 customer.name 到 GROUP BY 子句。 在 MySQL 中,名称是多余的。

    不过,这似乎……错了。

    【讨论】:

    • 你是对的,它似乎是错误的。它是!虽然我确信有一些例外情况,正如上面 Bill Karwin 所指出的那样,但我经常看到开发人员对数据不够了解或该功能如何真正工作,使用不正确的 group by 子句编写查询并得到不好的结果。此功能应默认关闭,并允许使用查询选项故意覆盖,以便在工程师被告知足以使用它的情况下使用。
    • 这并不比让SELECT * FROM table1 以给定的一致顺序返回结果更“错误”:这是一个特性,而不是一个错误。
    • @kmoser 这显然是“更错误”(更糟糕:))。 SQL 被定义为基于集合的语言,集合中元素的顺序无关紧要。只要表中的记录不变,SELECT * FROM table1 就会返回相同的记录集。相比之下,GROUPed 查询将返回不同的记录集,具体取决于记录插入表的顺序。这绝对是错误的。与许多其他与 mysql 相关的事情类似,这是 mysql 试图作为“功能”出售的一个危险陷阱。
    【解决方案2】:

    确实,此功能允许一些模棱两可的查询,并默默地返回一个结果集,其中包含从该列中选择的任意值。在实践中,它往往是首先物理存储的组内行中的值。

    如果您只选择在功能上依赖于 GROUP BY 条件中的列的列,则这些查询不会有歧义。换句话说,如果定义组的每个值只能有一个“模糊”列的不同值,则没有问题。此查询在 Microsoft SQL Server(和 ANSI SQL)中是非法的,即使它在逻辑上不会导致歧义:

    SELECT AVG(table1.col1), table1.personID, persons.col4
    FROM table1 JOIN persons ON (table1.personID = persons.id)
    GROUP BY table1.personID;
    

    另外,MySQL 有一个 SQL 模式,使其符合标准:ONLY_FULL_GROUP_BY

    FWIW,SQLite 也允许这些模棱两可的 GROUP BY 子句,但它从组中的 最后 行中选择值。


    至少在我测试的版本中。 任意意味着 MySQL 或 SQLite 将来可能会改变它们的实现,并有一些不同的行为。因此,在这种模棱两可的情况下,您不应依赖当前的行为方式。最好将您的查询重写为确定性而不是模棱两可。这就是 MySQL 5.7 现在默认启用 ONLY_FULL_GROUP_BY 的原因。

    【讨论】:

    • 我想说这并不完全正确。从 ANSI SQL-99 开始,所选字段必须是聚合的,在功能上依赖于 group by 子句。因此,在按 user_id 分组时选择 user_name 是完全可以的。 SQL Server 和 Oracle 不遵守这一点,因为它们不允许在 group by 列表中只有 user_id 时选择 user_name;而 MySQL 不遵守,因为它不检查选择的每一列是否真的在功能上依赖于 user_id。
    • @ThorstenKettner,谢谢,你是对的。 MySQL 5.7 进行了改进,在支持 ANSI SQL 的情况下更加智能。
    • 这里还有一篇文章描述了这个问题:percona.com/blog/2006/09/06/…
    【解决方案3】:
    select * from personel where p_id IN(select
    min(dbo.personel.p_id)
    FROM
    personel
    GROUP BY dbo.personel.p_adi)
    

    【讨论】:

    • 这绝对不能回答问题
    • @Ojen 它没有,但它有点解释了正在发生的事情。上面的代码是如何使用标准 SQL 对这种非标准行为进行建模的示例。
    【解决方案4】:

    假设您有这样的查询:

    SELECT g, v 
    FROM t
    GROUP BY g;
    

    在这种情况下,对于g 的每个可能值,mysql 选择v 的对应值之一。

    但是,选择哪一个取决于某些情况。

    我在某处读到,对于每组 g,v 的第一个值被保留,按照记录插入表 t 的顺序。

    这很丑陋,因为表中的记录应该被视为一个 set,其中元素的顺序无关紧要。这太“mysql-ish”了……

    如果您想确定要保留 v 的哪个值,您需要像这样为 t 应用子选择:

    SELECT g, v 
    FROM (
        SELECT * 
            FROM t 
            ORDER BY g, v DESC
    ) q
    GROUP BY g;
    

    通过这种方式,您可以定义外部查询处理子查询记录的顺序,因此您可以相信它将为g 的各个值选择v 的哪个值。

    但是,如果您需要一些 WHERE 条件,则要非常小心。如果您将 WHERE 条件添加到子查询,那么它将保持该行为,它将始终返回您期望的值:

    SELECT g, v 
    FROM (
        SELECT * 
            FROM t 
            WHERE g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9' 
            ORDER BY g, v DESC
    ) q
    GROUP BY g;
    

    这是您所期望的,子选择过滤器并对表格进行排序。它保留g 具有给定值的记录,外部查询返回gv 的第一个值。

    但是,如果您将相同的 WHERE 条件添加到外部查询,那么您会得到一个不确定的结果:

    SELECT g, v 
    FROM (
        SELECT * 
            FROM t 
            -- WHERE g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9' 
            ORDER BY g, v DESC
    ) q
    WHERE g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9'
    GROUP BY g;
    

    令人惊讶的是,当您一次又一次地执行相同的查询时,您可能会得到 v 的不同值,这很奇怪。预期的行为是以适当的顺序从子查询中获取所有记录,在外部查询中过滤它们,然后选择与前面示例中选择的相同的记录。但事实并非如此。

    它看似随机地为v 选择一个值。如果我执行更多 (~20) 次但分布不均匀,则相同的查询为 v 返回不同的值。

    如果不添加外部 WHERE,而是指定 HAVING 条件,如下所示:

    SELECT g, v 
    FROM (
        SELECT * 
            FROM t1 
            -- WHERE g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9' 
            ORDER BY g, v DESC
    ) q
    -- WHERE g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9'
    GROUP BY g
    HAVING g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9';
    

    然后您再次获得一致的行为。

    结论:我建议完全不要依赖这种技术。如果您真的想要/需要避免外部查询中的 WHERE 条件。如果可以的话,在内部查询中使用它,或者在外部查询中使用 HAVING 子句。

    我用这些数据对其进行了测试:

    CREATE TABLE t1 (
        v INT,
        g VARCHAR(36)
    );
    
    INSERT INTO t1 VALUES (1, '737a8783-110c-447e-b4c2-1cbb7c6b72c9');
    INSERT INTO t1 VALUES (2, '737a8783-110c-447e-b4c2-1cbb7c6b72c9');
    

    在 mysql 5.6.41 中。

    也许这只是一个在较新版本中得到/修复的错误,如果您有使用较新版本的经验,请提供反馈。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-28
      • 1970-01-01
      • 2020-11-19
      相关资源
      最近更新 更多