WHERE 和 HAVING 是 SQL 中两个重要的子句。无论您是在编写非常高级的查询还是非常简单的查询,都会需要用到它们。可以把 WHERE 和 HAVING 看作兄弟:它们的作用相似(过滤),而且经常一起出现。但就像兄弟一样,它们各自有不同的特性和独特的角色。
如果您正在理清 WHERE 和 HAVING 的用法,建议先从我们的 SQL Fundamentals 技能路径开始系统学习。它将带您了解不同的 SQL 子句、SQL 的执行顺序 、SQL 查询优化以及其他许多关键主题。
简要结论:WHERE vs. HAVING
很多人困惑的根源在于:WHERE 作用于行级数据,而 HAVING 作用于分组后的数据。可遵循以下准则:
一个快速示例来说明差异
让我们看一个快速示例。若您想跟着操作,可以从这个 GitHub 仓库 下载房产租赁数据集。
现在,假设我们要返回数据集中租金低于 $500 的所有房源。数据集的每一行(每行一个房源)的明细层级与查询条件的明细层级一致。因此,我们在以下查询中使用 WHERE:
SELECT *
FROM rentals
WHERE rental_price < 500 技术支持
结果如下所示。请注意共返回了 118 行。
使用 WHERE 进行简单的行级过滤。图片来源:作者。
现在,假设我们并不想要房源表,而是想要城市表,其中城市的 平均 租金低于 $2,700。此时每一行不再对应单个房源,而是对行进行分组,并将租金聚合为每个城市的平均租金。 因此,我们使用 HAVING,如下所示:
SELECT city, AVG ( rental_price) AS average_rent
FROM rentals
GROUP BY city
HAVING AVG ( rental_price) < 2700 ; 技术支持
结果如下。请注意只有一条记录。
使用 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 的房源。
排除 Cairo 和 Giza 的房源。图片来源:作者。
WHERE 的使用场景
鉴于 WHERE 可与 SELECT、UPDATE 和 DELETE 语句配合使用,可以预见它适用于三类场景:行级过滤、数据检索与数据操作。
行级过滤
我们可以基于一个或多个条件过滤表中的行。以下查询将行过滤为仅包含别墅类型的房源。
SELECT *
FROM rentals
WHERE type = ‘villa’; 技术支持
选择别墅房源。图片来源:作者。
数据检索
WHERE 可用于检索我们所需的特定数据点。这类似于行级过滤,但更具体。假设我们需要知道在开罗 2022 年 1 月 1 日可用的房源 ID,可以使用以下查询:
SELECT property_id
FROM rentals
WHERE available_date = '2022-01-01'
AND city = 'Cairo' ; 技术支持
检索单个数据点。图片来源:作者。
数据操作
最后,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 ; 技术支持
将 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 子句在分组或聚合之前应用。正因如此,它能在查询早期就减少需处理的行数,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 的良好实践。对于大数据集尤为重要。
高选择性: 只按需要过滤到的值进行筛选,不要多做。这样可减少结果中的行数,从而提升查询效率。
保持简单: 去除不必要的条件、聚合和类型转换。这些额外操作都会带来计算开销。
尽早过滤: 如果要进行分组级聚合,先用 WHERE 预过滤行,以加快分组过程。这样可以减少需要在分组中处理的行数。
对比表
让我们用一张表来总结以上要点:
对比项 WHERE HAVING 主要用途 行级过滤 分组级过滤 基本语法 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、DELETE SELECT 条件限制 不能包含聚合函数 必须基于聚合函数 使用场景 行级过滤 数据检索 数据操作 分组级过滤 单行过滤 子查询 可与子查询配合 应写作 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 职业路径。
WHERE和HAVING是 SQL 中两个重要的子句。无论您是在编写非常高级的查询还是非常简单的查询,都会需要用到它们。可以把WHERE和HAVING看作兄弟:它们的作用相似(过滤),而且经常一起出现。但就像兄弟一样,它们各自有不同的特性和独特的角色。如果您正在理清
WHERE和HAVING的用法,建议先从我们的 SQL Fundamentals 技能路径开始系统学习。它将带您了解不同的 SQL 子句、SQL 的执行顺序、SQL 查询优化以及其他许多关键主题。简要结论:WHERE vs. HAVING
很多人困惑的根源在于:
WHERE作用于行级数据,而HAVING作用于分组后的数据。可遵循以下准则:WHERE在任何分组或聚合发生之前过滤行。它应用于单独的行,不能与聚合函数一起使用。HAVING在完成分组与聚合之后过滤分组。它应用于聚合函数的结果,并与GROUP BY配合使用。一个快速示例来说明差异
让我们看一个快速示例。若您想跟着操作,可以从这个 GitHub 仓库下载房产租赁数据集。
现在,假设我们要返回数据集中租金低于 $500 的所有房源。数据集的每一行(每行一个房源)的明细层级与查询条件的明细层级一致。因此,我们在以下查询中使用
WHERE:结果如下所示。请注意共返回了 118 行。
使用 WHERE 进行简单的行级过滤。图片来源:作者。
现在,假设我们并不想要房源表,而是想要城市表,其中城市的平均租金低于 $2,700。此时每一行不再对应单个房源,而是对行进行分组,并将租金聚合为每个城市的平均租金。因此,我们使用
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搭配使用的WHERE,往往是最容易与HAVING混淆的场景。UPDATE 语句中的 WHERE
此外,
WHERE在UPDATE语句中用于精准定位需要更新的行,语法如下:DELETE 语句中的 WHERE
WHERE也常用于DELETE语句,定位需要删除的记录(行),如下所示:如何编写 WHERE 条件
WHERE条件写作简单的逻辑表达式。由三部分组成:变量/操作数、条件,以及值/结果。我们来看WHERE条件可用的对比与逻辑运算符。下面是一个示例,将
NOT与IN比较运算符一起使用:该语句将返回所有既不在 Cairo 也不在 Giza 的房源。
排除 Cairo 和 Giza 的房源。图片来源:作者。
WHERE 的使用场景
鉴于
WHERE可与SELECT、UPDATE和DELETE语句配合使用,可以预见它适用于三类场景:行级过滤、数据检索与数据操作。行级过滤
我们可以基于一个或多个条件过滤表中的行。以下查询将行过滤为仅包含别墅类型的房源。
选择别墅房源。图片来源:作者。
数据检索
WHERE可用于检索我们所需的特定数据点。这类似于行级过滤,但更具体。假设我们需要知道在开罗 2022 年 1 月 1 日可用的房源 ID,可以使用以下查询:检索单个数据点。图片来源:作者。
数据操作
最后,
WHERE也是修改值与删除特定记录的有力助手。比如,我们发现编号为 171 的房源其实不允许宠物,可以在UPDATE语句中用WHERE修改pet_friendly列:如何在 SQL 中使用 HAVING 子句
现在,我们以同样的细致程度来介绍
HAVING子句。HAVING 的语法与语句
首先需要知道,
HAVING子句只能用于SELECT语句。因此,它唯一的语法如下:请注意,您可以添加多个分组列和多个聚合列。下文会有示例。
如何编写 HAVING 条件
与
WHERE类似,HAVING条件也写作逻辑表达式,但多了一个组件:聚合函数。因此,一个HAVING条件由 1)聚合函数,2)变量/操作数,3)比较运算符,4)值/结果 组成。HAVING 可用的聚合函数
SQL 主要有五个聚合函数,另有一个特殊情况的第六类函数。如下所示:
来试一个例子。这里我们将
COUNT()与GROUP BY搭配来创建频数表,并用HAVING条件进行过滤。具体来说,我们要找出提及次数不超过 150 次的城市,在本例背景下对应注册数量。可使用如下查询:将 COUNT() 与 HAVING 搭配使用。图片来源:作者。
比较与逻辑运算符
HAVING接受与WHERE相同的所有比较与逻辑运算符。唯一的不同在于,HAVING下适用的数据类型首先取决于所用的聚合函数,如上表所示。HAVING 的使用场景
与
WHERE不同,HAVING不能用于UPDATE和DELETE语句;HAVING仅用于数据检索。大致对应两种场景:分组级过滤
这是
HAVING的常见且常规用法。例如只返回平均租金低于 $2,700 的城市。单行过滤
这并不常见,但在没有
GROUP BY的情况下,HAVING也可用于返回单行聚合(例如某个指标或 KPI),前提是满足某个条件。下面例子中,仅当平均租金低于 $2,800 时才返回该平均值。若满足条件,将得到单行结果;否则返回空表。组合使用 WHERE 与 HAVING
WHERE与HAVING可以组合使用,以在聚合前后同时过滤表。在下面的例子中,我们返回每个城市的房源数量,只统计允许宠物的房源,并仅保留房源数量超过 80 的城市。WHERE 与 HAVING 的基础组合。图片来源:作者。
WHERE 与 HAVING 在性能上的差异
这个查询效率较低,因为
rental_price条件不依赖任何聚合——它是在过滤单独的行。该查询会先按城市对所有租赁记录分组并计数,然后 再过滤结果,这会处理许多不必要的行。基于此,下面这个查询会更快:WHERE 与 HAVING 的优化建议
基于以上内容,我们可以归纳出一些优化使用
WHERE与HAVING的良好实践。对于大数据集尤为重要。高选择性: 只按需要过滤到的值进行筛选,不要多做。这样可减少结果中的行数,从而提升查询效率。
保持简单: 去除不必要的条件、聚合和类型转换。这些额外操作都会带来计算开销。
尽早过滤: 如果要进行分组级聚合,先用
WHERE预过滤行,以加快分组过程。这样可以减少需要在分组中处理的行数。对比表
让我们用一张表来总结以上要点:
结论
本文探讨了 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 职业路径。