好的,各位观众老爷们,欢迎来到今天的 “SQL 瞎侃” 现场!今天咱们聊聊 SQL 查询里一对形影不离,但又经常让人傻傻分不清的好基友:WHERE
和 HAVING
。
俗话说得好,“英雄难过美人关,程序员难过 SQL 关”。SQL 这玩意儿,看似简单,实则内藏乾坤。特别是 WHERE
和 HAVING
这俩,稍不留神,就会掉进坑里,轻则查询效率低下,重则结果南辕北辙。
今天,我就化身成段子手,哦不,是 SQL 专家,用最通俗易懂的语言,最生动形象的例子,把 WHERE
和 HAVING
扒个底朝天,让大家彻底搞懂它们之间的爱恨情仇,以及如何用它们写出高效优雅的 SQL 语句。
开场白:WHERE 和 HAVING,一对“相爱相杀”的好基友
想象一下,你是一位经验丰富的厨师,准备用各种食材做一道美味佳肴。WHERE
子句就像是你挑选食材的过程,它负责从一堆原材料中,筛选出你需要的那些。而 HAVING
子句呢?它就像是你对已经做好的菜品进行二次筛选,比如你觉得某些菜品盐放多了,或者辣椒不够劲,就可以用 HAVING
把它们挑出来。
简单来说:
WHERE
:管“生前”事,负责从原始数据中筛选行。HAVING
:管“身后”事,负责对分组后的数据进行筛选。
是不是有点意思了?别急,好戏还在后头。
第一幕:WHERE 子句——“先下手为强”的筛选利器
WHERE
子句是 SQL 查询中最常用的筛选工具之一,它的作用是在数据分组之前,对原始数据行进行过滤。你可以把它想象成一个严格的门卫,只有符合条件的行才能进入你的查询结果集。
语法:
SELECT column1, column2, ...
FROM table_name
WHERE condition;
这里的 condition
可以是各种各样的表达式,比如:
- 比较运算符:
=
,>
,<
,>=
,<=
,<>
(或!=
) - 逻辑运算符:
AND
,OR
,NOT
LIKE
运算符:用于模糊匹配字符串IN
运算符:用于判断值是否在某个集合中BETWEEN
运算符:用于判断值是否在某个范围内IS NULL
运算符:用于判断值是否为空
举个栗子:
假设我们有一个 employees
表,包含员工的姓名、部门、工资等信息:
employee_id | name | department | salary |
---|---|---|---|
1 | 张三 | 研发部 | 8000 |
2 | 李四 | 市场部 | 6000 |
3 | 王五 | 研发部 | 10000 |
4 | 赵六 | 市场部 | 7000 |
5 | 孙七 | 财务部 | 9000 |
如果我们想查询所有研发部的员工信息,可以使用如下 SQL 语句:
SELECT *
FROM employees
WHERE department = '研发部';
这条语句就像一道指令,告诉数据库:“把 employees
表中 department
列的值等于 ‘研发部’ 的所有行都给我找出来!”
结果:
employee_id | name | department | salary |
---|---|---|---|
1 | 张三 | 研发部 | 8000 |
3 | 王五 | 研发部 | 10000 |
WHERE 子句的优化点:
- 索引: 如果
WHERE
子句中使用的列有索引,数据库可以利用索引快速定位到符合条件的行,从而提高查询效率。所以,要经常检查你的查询语句,看看是否可以利用索引进行优化。 - 条件顺序: 在
WHERE
子句中,将筛选力度最大的条件放在前面,可以更快地排除掉不符合条件的行,减少后续的计算量。就好比你要在一堆苹果里挑出红色的、大的、甜的苹果,你应该先挑红色的,这样可以快速排除掉绿色的苹果,然后再挑大的,最后挑甜的。 - 避免使用
OR
: 在某些情况下,OR
运算符可能会导致数据库无法使用索引,从而降低查询效率。可以尝试使用UNION ALL
或IN
运算符来替代OR
。
第二幕:HAVING 子句——“秋后算账”的筛选大师
HAVING
子句的作用是在数据分组之后,对分组后的结果进行过滤。它通常与 GROUP BY
子句一起使用,就像一个挑剔的美食评论家,专门点评已经做好的菜品。
语法:
SELECT column1, column2, ...
FROM table_name
WHERE condition -- 可选
GROUP BY column1, column2, ...
HAVING condition;
注意,HAVING
子句中的 condition
只能使用聚合函数(比如 COUNT()
, SUM()
, AVG()
, MAX()
, MIN()
)和 GROUP BY
子句中指定的列。
举个栗子:
还是上面的 employees
表,如果我们想查询平均工资高于 8000 的部门,可以使用如下 SQL 语句:
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 8000;
这条语句的执行过程是这样的:
- 首先,
GROUP BY department
将employees
表按照department
列进行分组。 - 然后,
AVG(salary)
计算每个部门的平均工资。 - 最后,
HAVING AVG(salary) > 8000
筛选出平均工资高于 8000 的部门。
结果:
department | avg_salary |
---|---|
财务部 | 9000 |
研发部 | 9000 |
HAVING 子句的优化点:
- 尽可能使用
WHERE
替代HAVING
: 如果可以在分组之前使用WHERE
子句进行筛选,尽量不要把筛选条件放在HAVING
子句中。因为WHERE
子句可以减少分组的数据量,从而提高查询效率。 - 索引: 虽然
HAVING
子句本身不能直接使用索引,但是如果GROUP BY
子句中使用的列有索引,可以提高分组的效率,从而间接提高HAVING
子句的效率。 - 避免复杂的表达式: 尽量避免在
HAVING
子句中使用复杂的表达式,因为这会增加计算量,降低查询效率。
第三幕:WHERE vs HAVING,终极 PK 大战!
说了这么多,相信大家对 WHERE
和 HAVING
已经有了初步的了解。但是,它们之间到底有什么区别呢?什么时候该用 WHERE
,什么时候该用 HAVING
呢?下面,我们就来一场终极 PK 大战,彻底搞清楚它们之间的区别。
特性 | WHERE | HAVING |
---|---|---|
作用对象 | 原始数据行 | 分组后的数据 |
执行顺序 | 在 GROUP BY 之前执行 |
在 GROUP BY 之后执行 |
使用场景 | 筛选原始数据,减少数据量 | 筛选分组后的数据,对聚合结果进行过滤 |
能否使用聚合函数 | 不能 | 可以 |
优化重点 | 索引、条件顺序、避免使用 OR |
尽可能使用 WHERE 替代、索引、避免复杂表达式 |
总结一下:
- 如果你想在分组之前筛选数据,用
WHERE
。 - 如果你想在分组之后筛选数据,用
HAVING
。 - 如果你想使用聚合函数进行筛选,必须用
HAVING
。
举个更形象的例子:
假设你是一位大学老师,要统计班级里各科成绩的平均分,并找出平均分超过 80 分的科目。
- 首先,你需要用
GROUP BY
子句按照科目进行分组。 - 然后,你需要用
AVG()
函数计算每个科目的平均分。 - 最后,你需要用
HAVING
子句筛选出平均分超过 80 分的科目。
如果你想找出所有数学成绩高于 90 分的学生,你需要用 WHERE
子句进行筛选。
第四幕:实战演练,手把手教你写出高效 SQL
光说不练假把式,下面我们来几个实战演练,巩固一下今天所学的知识。
场景一:
假设我们有一个 orders
表,包含订单的订单ID、用户ID、订单金额、下单时间等信息。
order_id | user_id | amount | order_time |
---|---|---|---|
1 | 1 | 100 | 2023-10-26 10:00:00 |
2 | 2 | 200 | 2023-10-26 11:00:00 |
3 | 1 | 300 | 2023-10-26 12:00:00 |
4 | 3 | 400 | 2023-10-26 13:00:00 |
5 | 2 | 500 | 2023-10-26 14:00:00 |
现在我们需要查询所有订单金额大于 200 的用户的 ID 和订单总金额。
SELECT user_id, SUM(amount) AS total_amount
FROM orders
WHERE amount > 200
GROUP BY user_id;
这条语句先用 WHERE
子句筛选出订单金额大于 200 的订单,然后用 GROUP BY
子句按照用户 ID 进行分组,最后用 SUM()
函数计算每个用户的订单总金额。
场景二:
还是上面的 orders
表,现在我们需要查询订单总金额大于 500 的用户的 ID 和订单总金额。
SELECT user_id, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id
HAVING SUM(amount) > 500;
这条语句先用 GROUP BY
子句按照用户 ID 进行分组,然后用 SUM()
函数计算每个用户的订单总金额,最后用 HAVING
子句筛选出订单总金额大于 500 的用户。
场景三:
现在我们需要查询所有在 2023-10-26 下单,且订单金额大于 100 的订单,并按照用户ID分组,查询订单数量大于1的用户ID和订单数量。
SELECT user_id, COUNT(*) AS order_count
FROM orders
WHERE order_time BETWEEN '2023-10-26 00:00:00' AND '2023-10-26 23:59:59' AND amount > 100
GROUP BY user_id
HAVING COUNT(*) > 1;
这条语句结合了 WHERE
和 HAVING
,先用 WHERE
过滤日期和金额,再用 GROUP BY
分组,最后用 HAVING
过滤订单数量。
第五幕:总结与展望
今天,我们一起学习了 WHERE
和 HAVING
子句的区别和用法,并通过一些实战演练,巩固了所学的知识。希望大家以后在写 SQL 查询的时候,能够灵活运用这两个好基友,写出高效优雅的 SQL 语句。
记住,WHERE
管“生前”事,HAVING
管“身后”事。尽可能使用 WHERE
替代 HAVING
,并注意索引的使用,才能写出性能优良的 SQL 代码。
SQL 的世界博大精深,还有很多有趣的知识等待我们去探索。下次有机会,我们再一起聊聊 SQL 优化、索引设计、事务处理等话题。
感谢大家的观看,我们下期再见! 拜拜! 👋