打开网易新闻 查看精彩图片

很多人刚学 SQL 的时候,最容易卡住的不是 SELECT,而是这几个关键字到底谁先写、谁先执行:

  • WHERE
  • GROUP BY
  • HAVING
  • ORDER BY

看起来只是几个子句,真写题的时候却经常一脸懵:

  • 到底先过滤还是先分组?
  • WHERE 和 HAVING 有什么区别?
  • 为什么有时候能用别名,有时候又报错?
  • 明明语句写对了,结果却不对?

这篇文章就用最常见的单表查询场景,把这几个知识点彻底讲透。看完之后,你不光能写 SQL,还能真正理解它“是怎么跑起来的”。

一、先搞清楚:SELECT 语句到底怎么写

单表查询里,最常见的完整结构如下:

SELECT 字段, 聚合函数(字段)FROM 表名WHERE 条件GROUP BY 分组字段HAVING 分组后的条件ORDER BY 排序字段 ASC|DESC;

它的书写顺序是固定的:

  1. SELECT
  2. FROM
  3. WHERE
  4. GROUP BY
  5. HAVING
  6. ORDER BY

这个顺序不能乱。

比如下面这种就是错的:

SELECT job, MIN(sal)FROM empHAVING MIN(sal) < 2000GROUP BY job;

因为 HAVING 不能写在 GROUP BY 前面。

二、书写顺序和执行顺序,不是一回事

这是 SQL 初学者最容易忽略的一点。

虽然你写 SQL 的时候是从 SELECT 开始写,但数据库真正执行的时候,并不是先执行 SELECT。

SQL 的常见执行顺序

FROMWHEREGROUP BYSELECTHAVINGORDER BY

你可以这么理解:

1)FROM:先确定从哪张表取数据

先找到数据源。

2)WHERE:先对原始数据做第一次过滤

这是行级过滤,发生在分组之前。

3)GROUP BY:把过滤后的数据分组

按某个字段,或者多个字段进行聚合统计。

4)SELECT:选择要显示的列

这时候才真正决定最终显示哪些字段和计算结果。

5)HAVING:对分组后的结果再过滤一次

这是组级过滤

6)ORDER BY:最后排序

排序永远是最后一步。

三、WHERE 和 HAVING,到底差在哪?

这是面试和考试里都非常高频的点。

WHERE

  • 作用对象:原始记录
  • 执行时机:分组前
  • 适合场景:普通条件过滤

例如:

SELECT *FROM empWHERE sal > 2000;

表示先把工资大于 2000 的员工筛出来。

HAVING

  • 作用对象:分组后的结果
  • 执行时机:分组后
  • 适合场景:对聚合结果进行过滤

例如:

SELECT deptno, AVG(sal)FROM empGROUP BY deptnoHAVING AVG(sal) > 2000;

表示先按部门分组,再筛选平均工资大于 2000 的部门。

一句话记忆

WHERE 过滤行,HAVING 过滤组。

这句话非常重要,很多 SQL 题本质上就在考这个。

四、单表查询实战:4个经典练习一次吃透

下面直接上实战,用 emp 表来演示。假设表结构里有这些常见字段:

  • empno:员工编号
  • ename:员工姓名
  • job:职位
  • sal:工资
  • deptno:部门编号
练习1:列出工资最小值小于 2000 的职位题目拆解

这道题别急着写代码,先读懂意思:

  • “工资最小值”说明要用聚合函数 MIN(sal)
  • “职位”说明要按 job 分组
  • “最小值小于 2000”说明要对分组后的结果过滤,所以要用 HAVING
SQL 写法

SELECT job, MIN(sal) AS min_salFROM empGROUP BY jobHAVING MIN(sal) < 2000;
示例运行结果

JOB        MIN_SALCLERK      800SALESMAN   1600ANALYST    1000
结果含义:这些职位对应的最低工资低于 2000。
这题为什么不能用 WHERE?

因为 MIN(sal) 是分组后才算出来的结果,WHERE 阶段根本还没分组,自然也拿不到聚合结果。

错误示例

SELECT job, MIN(sal)FROM empWHERE MIN(sal) < 2000GROUP BY job;

这类写法会直接报错。

练习2:列出平均工资大于 1200 的“部门+职位”组合题目拆解

这题的关键在于:不是按部门分组,也不是按职位分组,而是按部门和职位的组合分组。

也就是说,一个分组单位是:

  • 某部门
  • 某职位

所以这里要使用多字段分组

SQL 写法

SELECT deptno, job, AVG(sal) AS avg_salFROM empGROUP BY deptno, jobHAVING AVG(sal) > 1200ORDER BY deptno ASC;
示例运行结果

DEPTNO  JOB        AVG_SAL10      MANAGER    245010      PRESIDENT  500020      ANALYST    300020      MANAGER    297530      MANAGER    285030      SALESMAN   1400
这题最容易犯的错错误1:把部门编号写成员工编号

很多人手一快写成 empno,这就完全变味了。

正确字段应该是:

deptno

不是:

empno
错误2:分组字段不完整

错误示例:

SELECT deptno, job, AVG(sal)FROM empGROUP BY deptno;

如果你 SELECT 里出现了 job,但 GROUP BY 里没有 job,很多数据库会直接报错。

因为数据库不知道同一个部门里多个 job 应该显示哪一个。

练习3:统计人数小于 4 的部门平均工资题目拆解

这题有两个目标:

  1. 统计每个部门的人数
  2. 找出人数小于 4 的部门
  3. 再展示这些部门的平均工资

所以这里要按 deptno 分组,同时用:

  • COUNT(1) 统计人数
  • AVG(sal) 统计平均工资
  • HAVING COUNT(1) < 4 做分组过滤
SQL 写法

SELECT deptno,COUNT(1) AS emp_count,AVG(sal) AS avg_salFROM empGROUP BY deptnoHAVING COUNT(1) < 4;
示例运行结果

DEPTNO  EMP_COUNT  AVG_SAL10      3          2916.67
这题为什么很经典?

因为它把两个聚合函数放在了一起:

  • COUNT() 负责判断人数
  • AVG() 负责输出平均工资

很多人一开始会误以为只能用一个聚合函数,其实完全可以在同一个查询里同时使用多个聚合统计。

练习4:统计各部门最高工资,并排除最高工资低于 3000 的部门

这道题的原意其实可以有两种理解,得看你想保留哪一类部门。

版本1:筛出“最高工资小于3000”的部门

如果你想找的是“哪些部门的最高工资还不到 3000”,那就这么写:

SELECT deptno, MAX(sal) AS max_salFROM empGROUP BY deptnoHAVING MAX(sal) < 3000;
示例结果

DEPTNO  MAX_SAL30      2850

这表示:30 号部门的最高工资只有 2850,说明整体薪资偏低。

版本2:排除最高工资低于3000的部门

如果题目说的是“排除最高工资小于 3000 的部门”,那实际保留的应该是最高工资大于等于 3000的部门。

写法应该是:

SELECT deptno, MAX(sal) AS max_salFROM empGROUP BY deptnoHAVING MAX(sal) >= 3000;
示例结果

DEPTNO  MAX_SAL10      500020      3000
这道题提醒我们什么?

做 SQL 题时,真正难的经常不是语法,而是审题

一定要看清楚题目到底是:

  • 筛选满足条件的组
  • 还是排除不满足条件的组

差一个字,SQL 逻辑就反了。

五、为什么有时候别名能用,有时候不能用?

很多人会注意到一个现象:

在 HAVING 里,有时可以写别名

SELECT deptno, AVG(sal) AS avg_salFROM empGROUP BY deptnoHAVING avg_sal > 2000;

某些数据库支持这种写法,因为 HAVING 执行时机晚于 SELECT。

但为了兼容性更好,建议你直接写完整表达式:

HAVING AVG(sal) > 2000

这样更稳。

在 WHERE 里通常不能直接用别名

因为 WHERE 执行在前,SELECT 里的别名此时还没生成。

比如:

SELECT sal * 12 AS annual_salFROM empWHERE annual_sal > 20000;

这类写法通常不成立。

六、写聚合查询时,最实用的审题方法

以后碰到 SQL 聚合题,直接按这三步走,基本不容易错。

第一步:先确定按什么分组

题目里如果出现:

  • 每个部门
  • 每种职位
  • 每个部门和职位组合

这些词,通常就是在提示你 GROUP BY 应该写什么。

第二步:再确定统计什么

题目里如果出现:

  • 平均工资 → AVG(sal)
  • 最低工资 → MIN(sal)
  • 最高工资 → MAX(sal)
  • 人数 → COUNT(1) 或 COUNT(*)

这一步决定聚合函数。

第三步:最后判断条件写 WHERE 还是 HAVING

这是最关键的判断标准:

  • 如果条件是针对原始字段,比如 sal > 1000,用 WHERE
  • 如果条件是针对统计结果,比如 AVG(sal) > 2000,用 HAVING
七、一个完整示例:把几个知识点串起来

假设现在有个需求:

查询 20 号和 30 号部门中,平均工资大于 1500 的职位,并按平均工资降序排列。
SQL 写法

SELECT deptno, job, AVG(sal) AS avg_salFROM empWHERE deptno IN (20, 30)GROUP BY deptno, jobHAVING AVG(sal) > 1500ORDER BY avg_sal DESC;
执行逻辑拆解第1步:FROM emp

从 emp 表取数据。

第2步:WHERE deptno IN (20, 30)

先把 20 号和 30 号部门之外的数据过滤掉。

第3步:GROUP BY deptno, job

按“部门+职位”组合分组。

第4步:SELECT deptno, job, AVG(sal)

计算每组平均工资,并选择要显示的列。

第5步:HAVING AVG(sal) > 1500

只保留平均工资大于 1500 的组。

第6步:ORDER BY avg_sal DESC

按平均工资从高到低排序。

示例运行结果

DEPTNO  JOB        AVG_SAL20      ANALYST    300020      MANAGER    297530      MANAGER    2850
八、几个非常容易踩坑的地方,提前避开1. WHERE 和 HAVING 混用

这是最常见的错。

错误思路:

  • 还没分组就想过滤平均工资
  • 已经是原始字段条件却硬写到 HAVING
2. 子句顺序写乱

正确顺序永远是:

SELECTFROMWHEREGROUP BYHAVINGORDER BY

别自己“自由发挥”。

3. GROUP BY 后,SELECT 中非聚合字段要保持一致

如果 SELECT 里出现普通字段,它通常就必须出现在 GROUP BY 中。

4. 审题不认真,逻辑写反

比如:

  • “小于 3000 的部门”
  • “排除小于 3000 的部门”

这两个 SQL 完全不是一回事。

九、建议收藏的几个高频模板1)按单字段分组统计

SELECT job, AVG(sal)FROM empGROUP BY job;
2)按多字段分组统计

SELECT deptno, job, AVG(sal)FROM empGROUP BY deptno, job;
3)分组后过滤

SELECT deptno, COUNT(*)FROM empGROUP BY deptnoHAVING COUNT(*) < 4;
4)先过滤再分组

SELECT deptno, AVG(sal)FROM empWHERE sal > 1000GROUP BY deptno;
5)分组后排序

SELECT deptno, AVG(sal) AS avg_salFROM empGROUP BY deptnoORDER BY avg_sal DESC;
十、总结:真正把 SQL 聚合查询想明白

单表查询看起来只是几行 SQL,实际上考的是三件事:

1. 你有没有看懂题

先分组什么,再统计什么,最后过滤什么。

2. 你知不知道 WHERE 和 HAVING 的边界

一个管原始数据,一个管分组结果。

3. 你是否理解执行顺序

写在前面的,不一定先执行;写在后面的,也不一定最后参与逻辑判断。

如果把这三件事想透了,后面的多表查询、子查询、复杂统计都会顺很多。