📊 SQL 入门 Day 5:GROUP BY & HAVING — 开始分组分析

📌 一句话总结

GROUP BY 将数据按列分组,配合聚合函数做组内统计;HAVING 对分组后的结果做筛选,是 WHERE 的"聚合后版本"。


1. GROUP BY 基础

sql 复制代码
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;

执行顺序:

  1. FROM → 取表
  2. WHERE → 过滤行
  3. GROUP BY → 分组
  4. 聚合函数 → 每组计算
  5. SELECT → 选列
  6. ORDER BY → 排序
sql 复制代码
-- 多列分组
SELECT department_id, gender, COUNT(*) AS count
FROM employees
GROUP BY department_id, gender;
-- 每个部门+性别组合的行数

2. WHERE vs HAVING --- 执行时机完全不同

sql 复制代码
-- WHERE:聚合前过滤(筛行)
-- HAVING:聚合后过滤(筛组)

-- 正确写法:先 WHERE 过滤掉离职员工,再按部门聚合
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
WHERE status = '在职'     -- 聚合前过滤
GROUP BY department_id
HAVING AVG(salary) > 10000;  -- 聚合后筛组

-- ❌ 错误:WHERE 不能引用聚合结果
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
WHERE AVG(salary) > 10000   -- 聚合还没执行,AVG 不能用
GROUP BY department_id;
对比 WHERE HAVING
执行时机 GROUP BY 之前 GROUP BY 之后
能否用聚合函数 ❌ 不能 ✅ 可以
能否用别名 ❌ 不能(SELECT 还没执行) ❌ 不能(逻辑顺序上)
适用范围 行级别过滤 组级别过滤

⚠️ HAVING 中也不能用 SELECT 中的别名,因为 SQL 逻辑执行顺序中 HAVINGSELECT 之前。MySQL 和 SQLite 宽松允许,但 PG 严格报错。

sql 复制代码
-- MySQL/SQLite 宽松写法(不推荐)
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
HAVING avg_salary > 10000;    -- 可以用别名

-- 标准写法(推荐,所有数据库通用)
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 10000;   -- 重复写聚合表达式

3. 多列分组实战

sql 复制代码
-- 按年份+部门统计
SELECT
    EXTRACT(YEAR FROM hire_date) AS hire_year,
    department_id,
    COUNT(*) AS hired_count,
    ROUND(AVG(salary), 0) AS avg_salary
FROM employees
GROUP BY EXTRACT(YEAR FROM hire_date), department_id
ORDER BY hire_year DESC, department_id;

-- 按城市+职位统计
SELECT
    city,
    job_title,
    COUNT(*) AS employee_count,
    ROUND(AVG(salary), 0) AS avg_salary
FROM employees
GROUP BY city, job_title
HAVING COUNT(*) >= 5     -- 只显示人数 >= 5 的组
ORDER BY avg_salary DESC;

4. 常见报错与陷阱

陷阱 1:SELECT 的列没在 GROUP BY 里

sql 复制代码
-- ❌ 错误
SELECT name, department_id, AVG(salary)
FROM employees
GROUP BY department_id;
-- name 不在 GROUP BY 中,PG 直接报错,MySQL 随便取一个

-- ✅ 正确
SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id;

陷阱 2:WHERE 过滤聚合结果

sql 复制代码
-- ❌ 错误:想找部门平均薪资 > 10000
SELECT department_id, AVG(salary)
FROM employees
WHERE AVG(salary) > 10000
GROUP BY department_id;

-- ✅ 正确:用 HAVING
SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 10000;

陷阱 3:HAVING + WHERE 混用顺序

sql 复制代码
-- WHERE 先过滤低薪员工,再分组,再 HAVING 筛组
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
WHERE salary > 5000          -- 1. 排除低薪
GROUP BY department_id       -- 2. 分组
HAVING AVG(salary) > 15000  -- 3. 只看高薪组
ORDER BY avg_salary DESC;    -- 4. 排序

5. 实战案例

案例 1:部门人才分析

sql 复制代码
SELECT
    department_id,
    COUNT(*) AS 人数,
    ROUND(AVG(salary), 0) AS 平均薪资,
    MAX(salary) AS 最高薪资,
    MIN(salary) AS 最低薪资,
    ROUND(AVG(DATEDIFF(CURDATE(), hire_date)/365), 1) AS 平均司龄_年
FROM employees
WHERE status = '在职'
GROUP BY department_id
HAVING 人数 >= 3
ORDER BY 平均薪资 DESC;

案例 2:订单分析

sql 复制代码
-- 每个客户的消费统计
SELECT
    customer_id,
    COUNT(*) AS order_count,
    SUM(total_amount) AS total_spent,
    AVG(total_amount) AS avg_order,
    MAX(order_date) AS last_order_date
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY customer_id
HAVING order_count >= 5              -- 回头客
   AND total_spent > 10000           -- 高价值客户
ORDER BY total_spent DESC;

案例 3:ROLLUP 分组汇总(进阶)

sql 复制代码
-- MySQL/PostgreSQL: GROUP BY ROLLUP 自动加小计和总计
SELECT
    COALESCE(department_id, '所有部门') AS department,
    COALESCE(gender, '合计') AS gender,
    COUNT(*) AS count,
    AVG(salary) AS avg_salary
FROM employees
GROUP BY ROLLUP (department_id, gender);
-- 结果会多出行:部门小计行(gender=合计)+ 总计行(所有部门)

6. GROUP BY 的执行顺序细节

完整 SQL 逻辑顺序:

sql 复制代码
SELECT              -- 5
    department_id,
    AVG(salary) AS avg_salary
FROM employees      -- 1
WHERE salary > 0    -- 2
GROUP BY dept_id    -- 3
HAVING AVG(salary) > 10000  -- 4
ORDER BY avg_salary DESC    -- 6
LIMIT 5;            -- 7

这个顺序决定了为什么 WHERE 不能用聚合函数、为什么 HAVING 通常不能用别名。


📝 要点总结

概念 关键点
GROUP BY 按列分组,每组一行结果
WHERE 聚合前过滤行
HAVING 聚合后过滤组,可以用聚合函数
SELECT 非聚合列 必须出现在 GROUP BY 中
执行顺序 WHERE → GROUP BY → HAVING → SELECT → ORDER BY
ROLLUP 自动添加小计/总计行
NULL 分组 NULL 也会自成一组
相关推荐
l134062082357 小时前
空状态引导设计:Text + Button 组合与交互反馈
后端
2501_918582377 小时前
月统计面板:日记数 + 连续天数 + 主要心情
后端
夜微凉47 小时前
面试题高优版
后端
HONG````7 小时前
HarmonyOS ArkUI Badge 徽标与 Chip 标签:消息红点、数字徽标与可选中标签
后端
卷无止境8 小时前
Python itertools 盘点真正用得上的20个常用方法
后端
芒鸽8 小时前
HarmonyOS ArkUI Marquee 跑马灯与 Gauge 仪表盘:公告滚动与数据展示
后端
Gopher_HBo8 小时前
镜像服务门面模式
后端
一缕清烟在人间8 小时前
CalendarYearPage 年视图:12 个月微型日历网格
后端
xianjixiance_8 小时前
月份切换与年份导航:prevMonth / nextMonth 逻辑
后端