SQL 的 GROUP BY 子句能够将数据聚合到汇总行中,例如可以找到每个客户的总订单数或每个产品类别的平均评分。但是,如果目标是仅查看符合特定条件的分组结果,例如只关注下了 超过 10 份订单的客户,或者平均评分 高于 4.0 的产品类别,该如何实现呢?你可能最初会想到使用 WHERE 子句,就像我们在上一章中过滤单个行那样。然而,WHERE 有一个局限:它在聚合步骤发生 之前 过滤行。它对表中的原始数据进行操作,而不是对 GROUP BY 和 COUNT()、AVG() 或 SUM() 等聚合函数生成的汇总结果进行操作。这就是 HAVING 子句的作用。介绍 HAVING 子句HAVING 子句专门用于在 GROUP BY 子句完成工作并计算出聚合函数之后 过滤 结果。它允许你对每个分组的汇总数据应用条件。包含 HAVING 的查询基本结构如下:SELECT column_name(s), aggregate_function(column_name) FROM table_name WHERE condition -- 可选:在聚合 *之前* 过滤行 GROUP BY column_name(s) HAVING aggregate_condition -- 在聚合 *之后* 过滤分组 ORDER BY column_name(s); -- 可选:对最终结果进行排序注意顺序:WHERE 在 GROUP BY 之前,HAVING 在 GROUP BY 之后。WHERE 与 HAVING:主要区别理解何时使用 WHERE 以及何时使用 HAVING 对于编写涉及聚合的正确 SQL 查询来说很重要。WHERE 子句: 在行被分组和聚合 之前 过滤单个行。你在这里使用表中原始列上的条件。HAVING 子句: 在分组被 GROUP BY 创建并由聚合函数汇总 之后 过滤整个分组。你在这里使用聚合函数(例如 COUNT(*)、AVG(price))的结果或分组列本身的条件。这样来想:WHERE 决定哪些配料放入碗中进行混合,而 HAVING 决定哪些完成的碗(分组)值得保留。让我们用一个例子来说明。想象一个 Orders 表:OrderIDCustomerIDOrderTotal110150.002102120.00310175.00410330.00510280.00610160.007102200.00场景 1:查找下了超过 2 份订单的客户。我们需要统计每个客户的订单,然后根据该计数进行过滤。SELECT CustomerID, COUNT(OrderID) AS NumberOfOrders FROM Orders GROUP BY CustomerID HAVING COUNT(OrderID) > 2; -- 根据聚合计数过滤分组结果:CustomerID订单数量10131023在这里,GROUP BY CustomerID 首先按客户对行进行分组。然后 COUNT(OrderID) 计算每个客户的订单数量(101:3 份订单,102:3 份订单,103:1 份订单)。最后,HAVING COUNT(OrderID) > 2 过滤这些分组结果,只保留下了超过 2 份订单的客户。使用 WHERE COUNT(OrderID) > 2 会导致错误,因为 COUNT(OrderID) 在 WHERE 子句处理之后才计算。场景 2:查找总消费超过 $150 的客户。SELECT CustomerID, SUM(OrderTotal) AS TotalSpending FROM Orders GROUP BY CustomerID HAVING SUM(OrderTotal) > 150.00; -- 根据聚合总和进行过滤结果:CustomerID总消费101185.00102400.00同样,HAVING 在为每个 CustomerID 分组计算出 SUM(OrderTotal) 之后 过滤结果。场景 3:查找下了超过 1 份订单的客户,但只考虑订单总额超过 $50 的订单。在这里,我们需要同时使用 WHERE 和 HAVING。SELECT CustomerID, COUNT(OrderID) AS NumberOfQualifyingOrders, SUM(OrderTotal) AS TotalQualifyingSpending FROM Orders WHERE OrderTotal > 50.00 -- 在分组 *之前* 过滤单个订单 GROUP BY CustomerID HAVING COUNT(OrderID) > 1; -- 在聚合 *之后* 过滤分组让我们追踪一下:WHERE OrderTotal > 50.00:OrderID 为 1、2、3、5、6、7 的行被保留。行 4(OrderTotal = 30.00)被丢弃。GROUP BY CustomerID:剩余的行被分组:CustomerID 101: Orders 1, 3, 6CustomerID 102: Orders 2, 5, 7聚合函数:为每个分组计算 COUNT(OrderID) 和 SUM(OrderTotal):CustomerID 101: Count = 3, Sum = 50.00 + 75.00 + 60.00 = 185.00CustomerID 102: Count = 3, Sum = 120.00 + 80.00 + 200.00 = 400.00HAVING COUNT(OrderID) > 1:两个分组都满足此条件(3 > 1)。结果:CustomerID符合条件的订单数量符合条件的消费总额1013185.001023400.00操作顺序的可视化为了巩固区别,请考虑 SQL 处理这些子句的顺序:digraph G { rankdir=TB; ratio=1; nodesep=0.2; ranksep=0.3; node [shape=box, style=rounded, fontsize=8, margin="0.2,0.1", penwidth=1]; edge [fontsize=7]; Start [label="从 / 连接", shape=ellipse, style=filled, fillcolor="#e9ecef"]; Where [label="WHERE\n(过滤行)", style=filled, fillcolor="#a5d8ff"]; GroupBy [label="GROUP BY\n(分组行)", style=filled, fillcolor="#96f2d7"]; Agg [label="聚合函数\n(例如:COUNT, SUM)", style=filled, fillcolor="#ffec99"]; Having [label="HAVING\n(过滤分组)", style=filled, fillcolor="#ffd8a8"]; Select [label="SELECT", style=filled, fillcolor="#eebefa"]; Distinct [label="DISTINCT", style=filled, fillcolor="#bac8ff"]; OrderBy [label="ORDER BY", style=filled, fillcolor="#fcc2d7"]; Limit [label="LIMIT / OFFSET", style=filled, fillcolor="#ced4da", shape=ellipse]; Start -> Where [label="输入:表"]; Where -> GroupBy [label="输入:已过滤的行"]; GroupBy -> Agg [label="输入:已分组的行"]; Agg -> Having [label="输入:已聚合的分组"]; Having -> Select [label="输入:已过滤的分组"]; Select -> Distinct [label="输入:已选择的列/表达式"]; Distinct -> OrderBy [label="输入:唯一行"]; OrderBy -> Limit [label="输入:已排序的行"]; Limit -> End [label="最终结果集", style=dashed]; End [label="输出", shape=ellipse, style=filled, fillcolor="#dee2e6"]; } SQL 子句逻辑处理顺序的简化视图。WHERE 早期作用于单个行,而 HAVING 稍后作用于聚合分组。如图所示,WHERE 在 GROUP BY 和聚合函数计算 之前 操作,而 HAVING 在之后操作。总结来说,当你需要根据应用于 GROUP BY 创建的分组的聚合函数(COUNT、SUM、AVG、MIN、MAX)结果来过滤结果时,请使用 HAVING。如果你需要在聚合 之前 根据原始行中的值进行过滤,请使用 WHERE。有时,如第三个例子所示,你可能需要在同一个查询中同时使用 WHERE 和 HAVING,以在不同阶段实现所需的过滤。