跳至主要内容

SQL 中 WHERE 与 HAVING 的区别

了解在 SQL 查询中,WHERE 如何过滤行级数据,而 HAVING 如何在聚合后过滤分组数据,并掌握它们在 SQL 查询中的不同用法。
· 8分钟 阅读

使用 AI 探索

WHERE 和 HAVING 是 SQL 中两个重要的子句。无论您是在编写非常高级的查询还是非常简单的查询,都会需要用到它们。可以把 WHERE 和 HAVING 看作兄弟:它们的作用相似(过滤),而且经常一起出现。但就像兄弟一样,它们各自有不同的特性和独特的角色。

如果您正在理清 WHERE 和 HAVING 的用法,建议先从我们的 SQL Fundamentals 技能路径开始系统学习。它将带您了解不同的 SQL 子句、SQL 的执行顺序、SQL 查询优化以及其他许多关键主题。

简要结论:WHERE vs. HAVING

很多人困惑的根源在于:WHERE 作用于行级数据,而 HAVING 作用于分组后的数据。可遵循以下准则:

  • WHERE 在任何分组或聚合发生之前过滤行。它应用于单独的行,不能与聚合函数一起使用。

  • HAVING 在完成分组与聚合之后过滤分组。它应用于聚合函数的结果,并与 GROUP BY 配合使用。

一个快速示例来说明差异

让我们看一个快速示例。若您想跟着操作,可以从这个 GitHub 仓库下载房产租赁数据集。

现在,假设我们要返回数据集中租金低于 $500 的所有房源。数据集的每一行(每行一个房源)的明细层级与查询条件的明细层级一致。因此,我们在以下查询中使用 WHERE:

SELECT *
FROM rentals
WHERE rental_price < 500

结果如下所示。请注意共返回了 118 行。

使用 SQL WHERE 进行简单的行级过滤

使用 WHERE 进行简单的行级过滤。图片来源:作者。

现在,假设我们并不想要房源表,而是想要城市表,其中城市的平均租金低于 $2,700。此时每一行不再对应单个房源,而是对行进行分组,并将租金聚合为每个城市的平均租金。因此,我们使用 HAVING,如下所示:

SELECT city, AVG(rental_price) AS average_rent
FROM rentals
GROUP BY city
HAVING AVG(rental_price) < 2700;

结果如下。请注意只有一条记录。

使用 SQL HAVING 进行简单的分组过滤

使用 HAVING 进行简单的分组过滤。图片来源:作者。

WHERE、HAVING 与 SQL 的执行顺序

我们已经看到,WHERE 用于行级过滤,而 HAVING 用于聚合级过滤。理解这一差异也为我们学习SQL 子句的执行顺序打下基础。

WHERE 在 GROUP BY 之前、在 FROM(以及存在时的 JOIN)之后进行评估;它无法处理任何分组或聚合。WHERE 在聚合之前生效,因此它只能操作行级数据。

而 HAVING 在执行 GROUP BY 之后才起作用。这意味着它在表被转换并按不同于源表粒度的层级分组之后才生效。HAVING 作用于表的新、已转换版本。

如何在 SQL 中使用 WHERE 子句

让我们退一步,分别看看每个子句。先从 WHERE 子句开始。

WHERE 的语法与语句

WHERE 可用于三类 SQL 语句:SELECT、UPDATE 和 DELETE。

SELECT 语句中的 WHERE

在用于从数据库提取数据的 SELECT 语句中,WHERE 直接承担行级过滤的角色,通常位于 FROM 子句之后,如下所示:

SELECT column1, column2… etc.
FROM table_name
WHERE condition;

与 SELECT 搭配使用的 WHERE,往往是最容易与 HAVING 混淆的场景。

UPDATE 语句中的 WHERE

此外,WHERE 在 UPDATE 语句中用于精准定位需要更新的行,语法如下:

UPDATE table_name
SET column_name1 = value1, column_name2 = value2… etc.
WHERE condition;

DELETE 语句中的 WHERE

WHERE 也常用于 DELETE 语句,定位需要删除的记录(行),如下所示:

DELETE FROM table_name
WHERE condition;

如何编写 WHERE 条件

WHERE 条件写作简单的逻辑表达式。由三部分组成:变量/操作数、条件,以及值/结果。我们来看 WHERE 条件可用的对比与逻辑运算符。

运算符说明适用数据类型
=等于(日期为当日)数值、文本、日期/时间戳、布尔
<小于(日期早于)数值、日期/时间戳
>大于(日期晚于)数值、日期/时间戳
<=小于等于(日期为当日或更早)数值、日期/时间戳
>=大于等于(日期为当日或更晚)数值、日期/时间戳
<> (!=)不等于(日期不为当日)数值、文本、日期/时间戳、布尔
IN等于多个值之一(多个日期)数值、文本、日期/时间戳、布尔(尽管通常没意义)
LIKE匹配文本模式(使用通配符)文本
BETWEEN位于某个范围内数值、日期/时间戳
AND组合多个条件,需全部为真逻辑(布尔)
OR组合多个条件,至少一个为真逻辑(布尔)
NOT对条件取反逻辑(布尔)
IS NULL检查空值所有数据类型
IS NOT NULL检查非空值所有数据类型

下面是一个示例,将 NOT 与 IN 比较运算符一起使用:

SELECT *
FROM rentals
WHERE city NOT IN ('Cairo', 'Giza');

该语句将返回所有既不在 Cairo 也不在 Giza 的房源。

在 WHERE 中使用 NOT

排除 Cairo 和 Giza 的房源。图片来源:作者。

WHERE 的使用场景

鉴于 WHERE 可与 SELECT、UPDATE 和 DELETE 语句配合使用,可以预见它适用于三类场景:行级过滤、数据检索与数据操作。

行级过滤

我们可以基于一个或多个条件过滤表中的行。以下查询将行过滤为仅包含别墅类型的房源。

SELECT *
FROM rentals
WHERE type = ‘villa’;

在 WHERE 中使用 NOT

选择别墅房源。图片来源:作者。

数据检索

WHERE 可用于检索我们所需的特定数据点。这类似于行级过滤,但更具体。假设我们需要知道在开罗 2022 年 1 月 1 日可用的房源 ID,可以使用以下查询:

SELECT property_id
FROM rentals
WHERE available_date = '2022-01-01'
AND city = 'Cairo';

使用 WHERE 进行行级过滤

检索单个数据点。图片来源:作者。

数据操作

最后,WHERE 也是修改值与删除特定记录的有力助手。比如,我们发现编号为 171 的房源其实不允许宠物,可以在 UPDATE 语句中用 WHERE 修改 pet_friendly 列:

UPDATE rentals
SET pet_friendly = false
WHERE property_id = 171;

如何在 SQL 中使用 HAVING 子句

现在,我们以同样的细致程度来介绍 HAVING 子句。

HAVING 的语法与语句

首先需要知道,HAVING 子句只能用于 SELECT 语句。因此,它唯一的语法如下:

SELECT grouped_column, aggregate_function(aggregated_column)… etc.
FROM table_name
GROUP BY grouped_column
HAVING condition

请注意,您可以添加多个分组列和多个聚合列。下文会有示例。

如何编写 HAVING 条件

与 WHERE 类似,HAVING 条件也写作逻辑表达式,但多了一个组件:聚合函数。因此,一个 HAVING 条件由 1)聚合函数,2)变量/操作数,3)比较运算符,4)值/结果 组成。

HAVING 可用的聚合函数

SQL 主要有五个聚合函数,另有一个特殊情况的第六类函数。如下所示:

聚合函数适用数据类型
SUM()数值
AVG()数值
MIN()数值、文本、日期/时间戳
MAX()数值、文本、日期/时间戳
COUNT()数值、文本、日期/时间戳、布尔

来试一个例子。这里我们将 COUNT() 与 GROUP BY 搭配来创建频数表,并用 HAVING 条件进行过滤。具体来说,我们要找出提及次数不超过 150 次的城市,在本例背景下对应注册数量。可使用如下查询:

SELECT city, COUNT(*) AS properties_count
FROM rentals
GROUP BY city
HAVING COUNT(*) <= 150;

将 MAX() 与 HAVING 搭配使用

将 COUNT() 与 HAVING 搭配使用。图片来源:作者。

比较与逻辑运算符

HAVING 接受与 WHERE 相同的所有比较与逻辑运算符。唯一的不同在于,HAVING 下适用的数据类型首先取决于所用的聚合函数,如上表所示。

HAVING 的使用场景

与 WHERE 不同,HAVING 不能用于 UPDATE 和 DELETE 语句;HAVING 仅用于数据检索。大致对应两种场景:

分组级过滤

这是 HAVING 的常见且常规用法。例如只返回平均租金低于 $2,700 的城市。

SELECT city, AVG(rental_price) AS avg_price
FROM rentals
GROUP BY city
HAVING AVG(rental_price) < 2700;

单行过滤

这并不常见,但在没有 GROUP BY 的情况下,HAVING 也可用于返回单行聚合(例如某个指标或 KPI),前提是满足某个条件。下面例子中,仅当平均租金低于 $2,800 时才返回该平均值。若满足条件,将得到单行结果;否则返回空表。

SELECT AVG(rental_price) as avg_price
FROM rentals
HAVING AVG(rental_price) > 2800;

组合使用 WHERE 与 HAVING

WHERE 与 HAVING 可以组合使用,以在聚合前后同时过滤表。在下面的例子中,我们返回每个城市的房源数量,只统计允许宠物的房源,并仅保留房源数量超过 80 的城市。

SELECT city, COUNT(*) AS number_properties
FROM rentals
WHERE pet_friendly = true
GROUP BY city
HAVING COUNT(*) > 80;

将 WHERE 与 HAVING 组合使用

WHERE 与 HAVING 的基础组合。图片来源:作者。

WHERE 与 HAVING 在性能上的差异

我们知道 WHERE 子句在分组或聚合之前应用。正因如此,它能在查询早期就减少需处理的行数,WHERE 在过滤单独行时更高效。

另一方面,HAVING 子句在聚合之后应用,并基于分组数据过滤结果集。由于它在所有行被分组之后才处理数据,通常比 WHERE 效率低一些,但对于涉及聚合函数的条件,它是必不可少的。

来看这个查询:

SELECT city, COUNT(*)
FROM rentals
GROUP BY city
HAVING rental_price < 2700;

这个查询效率较低,因为 rental_price 条件不依赖任何聚合——它是在过滤单独的行。该查询会先按城市对所有租赁记录分组并计数,然后 再过滤结果,这会处理许多不必要的行。基于此,下面这个查询会更快:

SELECT city, COUNT(*)
FROM rentals
WHERE rental_price < 2700
GROUP BY city;

WHERE 与 HAVING 的优化建议

基于以上内容,我们可以归纳出一些优化使用 WHERE 与 HAVING 的良好实践。对于大数据集尤为重要。

  1. 高选择性: 只按需要过滤到的值进行筛选,不要多做。这样可减少结果中的行数,从而提升查询效率。

  2. 保持简单: 去除不必要的条件、聚合和类型转换。这些额外操作都会带来计算开销。

  3. 尽早过滤: 如果要进行分组级聚合,先用 WHERE 预过滤行,以加快分组过程。这样可以减少需要在分组中处理的行数。

对比表

让我们用一张表来总结以上要点:

对比项WHEREHAVING
主要用途行级过滤分组级过滤
基本语法SELECT column1, column2... FROM table_name WHERE condition;SELECT grouped_column, aggregate_function(aggregated_column)... FROM table_name GROUP BY grouped_column HAVING condition;
评估顺序在 GROUP BY 之前在 GROUP BY 之后
可用语句SELECT、UPDATE、DELETESELECT
条件限制不能包含聚合函数必须基于聚合函数
使用场景行级过滤 数据检索 数据操作分组级过滤 单行过滤
子查询可与子查询配合应写作 CTE

结论

本文探讨了 SQL 中 WHERE 与 HAVING 的主要区别:WHERE 子句在聚合之前过滤行,而 HAVING 子句在聚合之后过滤分组数据。我们也提到了一些不那么常见的差异,例如 WHERE 可用于 SELECT、UPDATE 和 DELETE 语句,而 HAVING 只能用于 SELECT。此外我们还简单谈及了性能问题。

DataCamp 的 SQL Basics 速查表对 WHERE 与 HAVING 子句进行了很好的总结,并提供了 SQL 过滤的指导。如果您刚开始学习 SQL,不妨看看 SQL Fundamentals 技能路径以及 Associate Data Analyst in SQL 职业路径。

Islam Salahuddin

作者

Islam Salahuddin

Islam 是 The KPI Institute 的数据顾问。拥有新闻学背景的他兴趣广泛,涵盖写作、哲学、媒体、技术与文化。

常见问题解答

WHERE 与 HAVING 有什么区别?

WHERE 执行行级过滤,而 HAVING 执行分组级过滤。

我可以在同一个查询中同时使用 WHERE 和 HAVING 吗?

可以,而且如果要使用 HAVING,强烈建议同时使用 WHERE。

WHERE 能像 HAVING 一样与聚合函数配合使用吗?

不可以,只有 HAVING 能与聚合函数配合使用。

WHERE 与 HAVING 是否使用相同的比较与逻辑运算符?

可以,两者使用相同的运算符,因为 WHERE 与 HAVING 条件都是以逻辑表达式形式编写。

可以在没有 GROUP BY 的情况下使用 HAVING 吗?

HAVING 通常在使用 GROUP BY 分组之后使用。唯一的例外是单值视图过滤(例如计算某个指标)。

主题