好的,各位观众老爷们,欢迎来到今天的 “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 优化、索引设计、事务处理等话题。
感谢大家的观看,我们下期再见! 拜拜! 👋