MySQL 零基础教程

MySQL GROUP BY 与 HAVING

GROUP BY 子句通过将指定列中具有相同值的行进行合并,把结果集组织成摘要行。虽然像 SUM()COUNT() 这样的聚合函数允许你在整个数据集上执行计算,但 GROUP BY 提供了细粒度的控制,让你能够将这些计算精确应用到数据的特定子集中。

1. 使用 GROUP BY 对数据进行分类

当你使用 GROUP BY 时,MySQL 处理结果集的方式是:收集分组列中具有相同值的所有行,并将它们视为一个单一的实体。

考虑一个包含 orders(订单)表的电子商务数据库。要计算每个客户产生的总收入,你不需要一个总计数值;你需要的是每个客户的合计。

SELECT 
    customer_id, 
    SUM(order_amount) AS total_spent
FROM orders
GROUP BY customer_id;

在这个执行过程中,MySQL 扫描 orders 表,识别出每一个唯一的 customer_id,为每一个 ID 创建一个虚拟的“桶(bucket)”,然后将 SUM() 函数应用于这些特定桶内找到的 order_amount 值。如果你省略了 GROUP BY 子句,MySQL 会报错或者返回代表整个表的单行数据,这取决于你的服务器配置(即 ONLY_FULL_GROUP_BY SQL 模式)。

2. 使用 HAVING 过滤汇总结果

WHERE 子句在行被分组之前对单个行进行过滤。如果你需要在聚合发生之后过滤结果——例如,仅找出那些总花费超过 1,000 美元的客户——你必须使用 HAVING 子句。

SELECT 
    customer_id, 
    SUM(order_amount) AS total_spent
FROM orders
GROUP BY customer_id
HAVING total_spent > 1000;

尝试使用 WHERE 来过滤聚合数据是初学者常见的错误。数据库引擎甚至在分组逻辑开始之前就执行了 WHERE,这意味着在那个阶段它根本没有“总花费”的概念。HAVING 充当了最终结果集的守门员。

3. 执行顺序与逻辑

在使用这些子句时,理解查询的生命周期至关重要。数据库按照特定的顺序处理子句:它首先识别源数据,过滤原始行,对它们进行分组,执行计算,最后过滤汇总的输出。

  1. FROM/JOIN:确定数据源
  2. WHERE:过滤原始行
  3. GROUP BY:将行组织成“桶”
  4. 聚合函数:执行 SUM、COUNT、AVG 等计算
  5. HAVING:过滤分组后的组(聚合结果)
  6. SELECT:输出最终确定的列
  7. ORDER BY:对结果进行排序

4. 结合多列使用 GROUP BY

你可以按多个列进行分组以创建层级式的汇总。例如,要查看按类别 (category) 和年份 (year) 划分的总销售额,你需要将这两个列都提供给 GROUP BY 子句。

SELECT 
    category, 
    YEAR(order_date) AS order_year, 
    COUNT(order_id) AS total_orders
FROM orders
GROUP BY category, YEAR(order_date)
ORDER BY order_year DESC, total_orders DESC;

当按多列分组时,MySQL 会为这些列值的每一个唯一组合生成一个唯一的行。请注意:在 SELECT 列表中包含的任何不属于聚合函数一部分的列,都必须包含在 GROUP BY 子句中;否则,数据库将无法确定该列具体要显示哪个值。