LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

两个SQL数据筛选WHERE子句与HAVING子句的对比

admin
2025年8月30日 12:49 本文热度 54

在SQL数据查询中,筛选是重要操作。WHERE和HAVING子句作为筛选工具,功能虽然相似,但适用场景和底层逻辑差异明显。下面从基础语法出发,结合场景示例、性能优化、面试考点等方面,详细对比分析WHERE和HAVING子句,仅供参考。

一、基础语法

1. WHERE子句:行级筛选的“前置过滤器”

定义:WHERE子句用在数据分组、聚合之前,对表中的单个行进行筛选,仅保留满足条件的行,参与后续操作(如查询、分组、聚合等)。

语法结构

-- 单表查询
SELECT 列名1, 列名2, ...
FROM 表名
WHERE 行级条件表达式;

-- 多表关联查询(可结合JOIN使用)
SELECT 表1.列名, 表2.列名
FROM 表1
JOIN 表2 ON 关联条件
WHERE 行级条件表达式;

支持的条件类型

  • 比较运算符:=(等于)、!=/<>(不等于)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于)
  • 逻辑运算符:AND(多条件同时满足)、OR(多条件满足其一)、NOT(否定条件)
  • 范围匹配:BETWEEN...AND...(闭区间范围)、IN(匹配集合中的值)
  • 模糊匹配:LIKE(配合%通配符匹配任意字符,_匹配单个字符)
2. HAVING子句:分组级筛选的“后置过滤器”

定义:HAVING子句仅用在GROUP BY分组之后,对分组结果进行筛选,筛选条件可基于聚合函数(如SUM()COUNT()等)。

语法结构

SELECT 分组列, 聚合函数(列名) AS 别名
FROM 表名
[WHERE 行级条件]  -- 可选,先筛选行
GROUP BY 分组列    -- 对行分组
HAVING 聚合条件    -- 筛选分组结果
[ORDER BY 列名];   -- 可选,排序

支持的条件类型

  • 聚合函数:COUNT(列名)(计数)、SUM(列名)(求和)、AVG(列名)(平均值)、MAX(列名)(最大值)等
  • 分组列:GROUP BY中指定的列名(不可使用未分组的列)

说明

  • 在部分数据库(如 MySQL)中,即使不使用GROUP BY,HAVING也可单独使用(此时默认将所有数据视为一个分组)。例如:
-- 不使用GROUP BY,HAVING筛选聚合结果(默认全表为一组)
SELECT COUNT(*) AS total FROM employees HAVING COUNT(*) > 10;
  • HAVING中可直接使用SELECT中定义的聚合函数别名(部分数据库支持)。例如:
-- 部分数据库支持(如MySQL):HAVING使用SELECT中的聚合别名
SELECT department, AVG(salary) AS avg_sal 
FROM employees 
GROUP BY department 
HAVING avg_sal > 8000; -- 直接使用别名avg_sal

并非所有数据库都支持(如 Oracle 要求HAVING中重复写聚合函数)

二、WHERE和HAVING子句差异对比

在SQL中,WHERE和HAVING子句都用于筛选数据,但它们在使用场景、作用对象、语法规则和性能等方面区别明显,具体如下:

1. 使用场景
  • WHERE:主要用于对表进行查询操作时,对表中的行进行筛选,在分组和聚合操作之前起作用,用于选择满足特定条件的单个行。
-- 示例:从employees表中筛选出工资大于5000的员工:
SELECT * FROM employees WHERE salary > 5000;
  • HAVING:通常用在分组(GROUP BY)操作之后,用于对分组后的结果进行筛选,基于聚合函数的结果来筛选分组。
-- 示例:找出平均工资大于8000的部门:
SELECT department, AVG(salary) AS avg_salary FROM employees GROUP BY department HAVING AVG(salary) > 8000;
2. 作用对象
  • WHERE:作用于基本表或视图中的每一行数据,也就是未分组的原始数据,根据指定条件决定是否将该行包含在结果集中。它不能直接与聚合函数一起使用,因为聚合函数是在分组之后对一组数据进行计算,而WHERE在分组之前执行。
  • HAVING:作用于由GROUP BY子句创建的分组,也就是分组后的聚合数据,根据聚合函数计算后的结果来决定是否保留该分组,主要用于筛选基于聚合结果的分组数据。
3. 语法规则
  • WHERE:紧跟在FROM子句之后、GROUP BY之前,语法结构为:SELECT 列名 FROM 表名 WHERE 条件表达式; ,条件表达式可以是比较运算符(如>、<、=)、逻辑运算符(如AND、OR)等组成的表达式,且不能包含聚合函数。
  • HAVING:紧跟在GROUP BY子句之后、ORDER BY之前,语法结构为:SELECT 列名 FROM 表名 GROUP BY 分组列名 HAVING 条件表达式; ,这里的条件表达式通常基于聚合函数(如SUM、AVG、COUNT等)的结果,也可以使用GROUP BY中指定的列名。
-- 示例:找出员工数量大于5的部门:
SELECT department, COUNT(*) AS employee_count FROM employees GROUP BY department HAVING COUNT(*) > 5;
4. 筛选时机
  • WHERE:在分组、聚合操作之前执行筛选,先对原始数据行进行过滤,再进行后续的分组和聚合。
  • HAVING:在分组、聚合操作之后执行筛选,先完成数据分组和聚合计算,再对得到的分组结果进行过滤。
5. 性能差异
  • WHERE:由于在数据分组之前对数据进行筛选,减少了参与后续操作的数据量,所以在性能上一般会优于HAVING。所以,能在WHERE中完成的筛选操作,尽量不要放到HAVING中。
  • HAVING:需要先进行分组操作,然后再对分组结果进行筛选,相对而言性能可能会稍差一些。但当筛选条件依赖于聚合函数的结果时,就必须用HAVING。
小结

对比角度WHERE子句HAVING子句
作用对象
单个行(未分组的原始数据)
分组结果(GROUP BY后的聚合数据)
使用位置
必须在FROM之后、GROUP BY之前
必须在GROUP BY之后、ORDER BY之前
能否用聚合函数
不能(聚合函数依赖分组,此时尚未分组)
能(专门用于筛选聚合结果)
筛选时机
分组、聚合操作之前
分组、聚合操作之后
适用场景
筛选原始数据中符合条件的行
筛选分组后聚合结果符合条件的分组

三、适用场景分析

为直观理解WHERE和HAVING子句差异,以下通过“员工表(employees)”模拟数据进行对比演示。

表结构与数据

id
name
department
salary
hire_year
1
张三
技术部
8000
2020
2
李四
技术部
12000
2018
3
王五
销售部
6000
2021
4
赵六
销售部
15000
2019
5
钱七
销售部
9000
2022
6
孙八
人事部
7000
2020

场景1:使用WHERE筛选行(分组前)

需求:查询2020年及之后入职的员工姓名和薪资。

SELECT name, salary
FROM employees
WHERE hire_year >= 2020;

结果

name
salary
张三
8000
王五
6000
钱七
9000
孙八
7000

说明:WHERE直接过滤原始行,仅保留hire_year >= 2020的记录,未涉及分组。

场景2:使用HAVING筛选分组(分组后)

需求:查询平均薪资大于等于10000的部门及平均薪资。

SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) >= 10000;

结果

department
avg_salary
销售部
10000

-- (6000+15000+9000)/3 = 10000
说明:先按部门分组计算平均薪资,再通过HAVING筛选出平均薪资>=10000的分组。

场景3:WHERE与HAVING结合使用

需求:查询2019年及之前入职的员工中,总薪资大于20000的部门及总薪资。

SELECT department, SUM(salary) AS total_salary
FROM employees
WHERE hire_year <= 2019  -- 先筛选2019年前入职的员工
GROUP BY department
HAVING SUM(salary) > 20000;  -- 再筛选总薪资>20000的部门

分步解析

  1. WHERE筛选:仅保留hire_year <= 2019的员工(李四、赵六);
  2. 分组:按部门分组(技术部:李四;销售部:赵六);
  3. 聚合:计算每组总薪资(技术部12000,销售部15000);
  4. HAVING筛选:总薪资>20000的分组(无符合条件的结果,故返回空)。

下面通过另外一个完整示例将数据处理流程说明、各阶段逻辑、对应 SQL 代码以及各阶段数据示例完整呈现。基于 SALES 表,按“筛选→分组→聚合筛选→结果呈现”逻辑处理数据,以下全流程说明:

1. 底层数据(FROM 阶段)

逻辑:从 SALES 表获取原始数据,为后续筛选、聚合打基础。

-- ① 底层数据:从SALES表取原始数据
SELECT *
FROM SALES;

SALES 数据集

YEAR
MONTH
DATE
CATEGORY
amount
2025
1
2025/1/1
苹果
2457
2025
1
2025/1/2
苹果
1346
2025
1
2025/1/3
苹果
4356
2025
2
2025/2/1
苹果
42500
2025
2
2025/2/2
苹果
7245
2025
2
2025/2/3
苹果
7365
2024
12
2024/12/14
苹果
73650

2. 底层筛选(WHERE 阶段)

逻辑:筛选 year = '2025' 且 category = '苹果' 的记录,缩小数据范围。

-- ② 底层筛选:在原始数据基础上按条件过滤
SELECT *
FROM SALES
WHERE year = '2025' AND category = '苹果';

筛选后的数据集

YEAR
MONTH
DATE
CATEGORY
amount
2025
1
2025/1/1
苹果
2457
2025
1
2025/1/2
苹果
1346
2025
1
2025/1/3
苹果
4356
2025
2
2025/2/1
苹果
42500
2025
2
2025/2/2
苹果
7245
2025
2
2025/2/3
苹果
7365

3. 聚合分组(GROUP BY 阶段)

逻辑:按 yearmonth 分组,计算每组 amount 总和,聚合数据。

-- ③ 聚合分组:对筛选后数据按年月分组并求和
SELECT year, month, category, SUM(amount) AS total_amount
FROM SALES
WHERE year = '2025' AND category = '苹果'
GROUP BY year, month, category;

聚合分组后的数据集

YEAR
MONTH
CATEGORY
total_amount
2025
1
苹果
8159
2025
2
苹果
57110

4. 聚合筛选(HAVING 阶段)

逻辑:对分组聚合结果二次筛选,仅保留 SUM(amount) > 50000 的分组。

-- ④ 聚合筛选:对分组聚合结果再过滤
SELECT year, month, category, SUM(amount) AS total_amount
FROM SALES
WHERE year = '2025' AND category = '苹果'
GROUP BY year, month, category
HAVING SUM(amount) > 50000;

聚合分组后再筛选的数据集

YEAR
MONTH
CATEGORY
total_amount
2025
2
苹果
57110

5. 呈现字段(最终 SELECT 阶段)

逻辑:精简输出字段,仅保留 yearmonth、聚合金额,清晰呈现结果。

-- ⑤ 呈现字段:简化结果列,输出最终需求数据
SELECT year, month, SUM(amount) AS total_amount
FROM SALES
WHERE year = '2025' AND category = '苹果'
GROUP BY year, month
HAVING SUM(amount) > 50000;

选择字段呈现结果集

YEAR
MONTH
total_amount
2025
2
57110

说明:上述 SQL 基于 MySQL 语法编写,若使用 Oracle、SQL Server 等数据库,需微调语法(如字符串引号、函数兼容等 ),但基本逻辑一致。

四、面试考点

问题1:为什么WHERE不能使用聚合函数?
答:聚合函数(如SUM())需要基于分组后的多行数据计算,而WHERE在分组前执行,此时尚无分组,无法调用聚合函数。

问题2:HAVING能否使用非聚合列?
答:只能使用GROUP BY中指定的分组列,或聚合函数。例如:

-- 错误:非分组列且非聚合函数
SELECT department, name, COUNT(*) 
FROM employees
GROUP BY department
HAVING name = '张三';  -- name未在GROUP BY中,报错
  
-- 正确:使用分组列
SELECT department, COUNT(*) 
FROM employees
GROUP BY department
HAVING department = '技术部';  -- 分组列可直接使用

五、性能优化技巧

1. 优先使用WHERE减少数据量
WHERE在分组前筛选,可减少参与分组和聚合的数据量,提升效率。例如:
需求“查询员工数量大于2的部门中,2020年后入职的员工数量”,应先通过WHERE筛选2020年后的员工,再分组:

-- 高效:先筛选行,再分组
SELECT department, COUNT(*) AS emp_count
FROM employees
WHERE hire_year >= 2020
GROUP BY department
HAVING COUNT(*) > 2;
  
-- 低效:先分组再筛选行(数据量更大)
SELECT department, COUNT(*) AS emp_count
FROM employees
GROUP BY department
HAVING COUNT(CASE WHEN hire_year >= 2020 THEN 1 END) > 2;

2. 避免在HAVING中使用复杂表达式
聚合函数的计算成本较高,若HAVING条件复杂(如嵌套函数),会增加分组后的计算压力,建议简化条件或提前通过子查询处理。

六、避坑指南

坑1:混淆分组列与非分组列
SELECT后若包含非分组列且非聚合函数,会导致结果不可预测(不同数据库处理逻辑不同)。例如:

-- 错误:name是非分组列且未聚合
SELECT department, name, AVG(salary)
FROM employees
GROUP BY department;  -- 部分数据库报错,部分返回随机name

说明: “SELECT后若包含非分组列且非聚合函数,会导致结果不可预测”这一规则的核心是 “SELECT后的非聚合列必须全部出现在GROUP BY中”(除非数据库开启了非标准模式,如 MySQL 的ONLY_FULL_GROUP_BY关闭时)。例如:

-- 正确:SELECT的非聚合列(department)在GROUP BY中
SELECT department, AVG(salary) FROM employees GROUP BY department;

-- 错误:SELECT的非聚合列(name)不在GROUP BY中
SELECT department, name, AVG(salary) FROM employees GROUP BY department;

坑2BETWEENIN的范围陷阱
WHERE中使用BETWEEN...AND...时注意是闭区间(包含两端值),而IN适用于离散值集合。例如:

-- 查询薪资在8000-12000之间的员工(包含8000和12000)
SELECT name, salary FROM employees WHERE salary BETWEEN 8000 AND 12000;
  
-- 查询薪资为8000、12000、15000的员工
SELECT name, salary FROM employees WHERE salary IN (8000, 12000, 15000);

坑3:HAVING中误用COUNT(*)COUNT(列名)
COUNT(*)统计所有行数(包括NULL),COUNT(列名)统计非NULL值。例如:
若某部门有1名员工薪资为NULL,COUNT(*)会算入,COUNT(salary)则忽略:

SELECT department, COUNT(*), COUNT(salary)
FROM employees
GROUP BY department;

七、总结

WHERE和HAVING的核心区别在于筛选时机作用对象

  • WHERE是“前置筛选”,作用于行,用于减少原始数据量;
  • HAVING是“后置筛选”,作用于分组,用于过滤聚合结果。

实际使用中,需根据是否涉及分组和聚合来选择:若需筛选单个行,用WHERE;若需筛选分组后的聚合结果,用HAVING。结合性能优化技巧和避坑指南,可写出更高效、更可靠的SQL查询。


阅读原文:原文链接


该文章在 2025/9/1 11:55:32 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved