理解 `WHERE` 与 `HAVING` 子句在查询处理中的差异与优化点

好的,各位观众老爷们,欢迎来到今天的 “SQL 瞎侃” 现场!今天咱们聊聊 SQL 查询里一对形影不离,但又经常让人傻傻分不清的好基友:WHEREHAVING

俗话说得好,“英雄难过美人关,程序员难过 SQL 关”。SQL 这玩意儿,看似简单,实则内藏乾坤。特别是 WHEREHAVING 这俩,稍不留神,就会掉进坑里,轻则查询效率低下,重则结果南辕北辙。

今天,我就化身成段子手,哦不,是 SQL 专家,用最通俗易懂的语言,最生动形象的例子,把 WHEREHAVING 扒个底朝天,让大家彻底搞懂它们之间的爱恨情仇,以及如何用它们写出高效优雅的 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 ALLIN 运算符来替代 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;

这条语句的执行过程是这样的:

  1. 首先,GROUP BY departmentemployees 表按照 department 列进行分组。
  2. 然后,AVG(salary) 计算每个部门的平均工资。
  3. 最后,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 大战!

说了这么多,相信大家对 WHEREHAVING 已经有了初步的了解。但是,它们之间到底有什么区别呢?什么时候该用 WHERE,什么时候该用 HAVING 呢?下面,我们就来一场终极 PK 大战,彻底搞清楚它们之间的区别。

特性 WHERE HAVING
作用对象 原始数据行 分组后的数据
执行顺序 GROUP BY 之前执行 GROUP BY 之后执行
使用场景 筛选原始数据,减少数据量 筛选分组后的数据,对聚合结果进行过滤
能否使用聚合函数 不能 可以
优化重点 索引、条件顺序、避免使用 OR 尽可能使用 WHERE 替代、索引、避免复杂表达式

总结一下:

  • 如果你想在分组之前筛选数据,用 WHERE
  • 如果你想在分组之后筛选数据,用 HAVING
  • 如果你想使用聚合函数进行筛选,必须用 HAVING

举个更形象的例子:

假设你是一位大学老师,要统计班级里各科成绩的平均分,并找出平均分超过 80 分的科目。

  1. 首先,你需要用 GROUP BY 子句按照科目进行分组。
  2. 然后,你需要用 AVG() 函数计算每个科目的平均分。
  3. 最后,你需要用 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;

这条语句结合了 WHEREHAVING,先用 WHERE 过滤日期和金额,再用 GROUP BY 分组,最后用 HAVING 过滤订单数量。

第五幕:总结与展望

今天,我们一起学习了 WHEREHAVING 子句的区别和用法,并通过一些实战演练,巩固了所学的知识。希望大家以后在写 SQL 查询的时候,能够灵活运用这两个好基友,写出高效优雅的 SQL 语句。

记住,WHERE 管“生前”事,HAVING 管“身后”事。尽可能使用 WHERE 替代 HAVING,并注意索引的使用,才能写出性能优良的 SQL 代码。

SQL 的世界博大精深,还有很多有趣的知识等待我们去探索。下次有机会,我们再一起聊聊 SQL 优化、索引设计、事务处理等话题。

感谢大家的观看,我们下期再见! 拜拜! 👋

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注