404 Not Found

404 Not Found


nginx

分组查询

分组查询

🌍 生活类比

想象你是一个班主任,手里有一沓成绩单。你想知道:

这就是 GROUP BY 的作用:先把数据分成若干小组,再对每个小组进行统计。上一课学的聚合函数是对整张表算一个值,GROUP BY 则是按类别分别算值。


🎯 核心概念

GROUP BY 基本语法

SQL
SELECT 列名, 聚合函数(列名)
FROM 表名
WHERE 条件
GROUP BY 列名;

执行逻辑:先按 GROUP BY 的列分组,再对每组执行聚合函数。

SQL
-- 每个部门有多少人
SELECT department_id, COUNT(*) AS 人数
FROM employees
GROUP BY department_id;

输出:

TEXT
department_id  人数
-------------  ----
1              3
2              2
3              2
NULL           1
💡 规则SELECT 中的列,要么出现在 GROUP BY 中,要么用聚合函数包裹。否则语义不明确,数据库会报错。

分组后聚合

GROUP BY 的核心模式是:分组 → 聚合

SQL
-- 每个部门的平均工资
SELECT department_id, AVG(salary) AS 平均工资
FROM employees
GROUP BY department_id;

常用的聚合组合:

SQL
SELECT department_id,
       COUNT(*) AS 人数,
       SUM(salary) AS 工资总额,
       AVG(salary) AS 平均工资,
       MAX(salary) AS 最高工资,
       MIN(salary) AS 最低工资
FROM employees
GROUP BY department_id;

多列分组

可以按多个列分组,只有当这些列的值同时相同时才算同一组:

SQL
-- 每个部门、每种状态的员工人数
SELECT department_id, status, COUNT(*) AS 人数
FROM employees
GROUP BY department_id, status;

输出:

TEXT
department_id  status    人数
-------------  --------  ----
1              active    3
2              active    1
2              inactive  1
3              active    2
💡 理解:多列分组类似于Excel中的多级分类汇总。先按部门分,再按状态分,每个部门+状态的组合形成一个组。

GROUP BY + ORDER BY

分组后可以排序:

SQL
-- 按部门统计平均工资,从高到低排序
SELECT department_id, AVG(salary) AS 平均工资
FROM employees
GROUP BY department_id
ORDER BY 平均工资 DESC;

WHERE vs HAVING — 过滤行 vs 过滤组

这是本课最重要的概念之一:

特性 WHERE HAVING
过滤对象 (分组前) (分组后)
执行时机 GROUP BY 之前 GROUP BY 之后
能否用聚合函数 ❌ 不能 ✅ 可以
配合使用 可以单独用 必须配合 GROUP BY
SQL
-- WHERE:先过滤行,再分组
-- 找出有3人以上的部门
SELECT department_id, COUNT(*) AS 人数
FROM employees
WHERE salary > 5000       -- 先排除工资低于5000的行
GROUP BY department_id;

-- HAVING:先分组,再过滤组
-- 找出人数>=3的部门
SELECT department_id, COUNT(*) AS 人数
FROM employees
GROUP BY department_id
HAVING COUNT(*) >= 3;     -- 过滤人数不足3的组
SQL
-- WHERE + HAVING 组合使用
-- 找出2024年入职、且平均工资>10000的部门
SELECT department_id, AVG(salary) AS 平均工资
FROM employees
WHERE hire_date >= '2024-01-01'   -- 先过滤行
GROUP BY department_id
HAVING AVG(salary) > 10000;      -- 再过滤组

执行顺序

SQL查询的书写顺序执行顺序不同:

TEXT
书写顺序:SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY
执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY

理解执行顺序对写出正确的SQL至关重要:

  1. FROM — 确定数据源
  2. WHERE — 逐行过滤
  3. GROUP BY — 分组
  4. HAVING — 过滤分组
  5. SELECT — 选择列、计算表达式
  6. ORDER BY — 排序
💡 关键:这就是为什么 WHERE 中不能用聚合函数——因为 WHERE 执行时,GROUP BY 还没执行,聚合结果还不存在。


📝 基本语法

SQL
-- 基本分组
SELECT 列名, 聚合函数(列名)
FROM 表名
GROUP BY 列名;

-- 多列分组
SELECT 列名1, 列名2, 聚合函数(列名)
FROM 表名
GROUP BY 列名1, 列名2;

-- WHERE + GROUP BY + HAVING
SELECT 列名, 聚合函数(列名)
FROM 表名
WHERE 行级条件
GROUP BY 列名
HAVING 聚合级条件
ORDER BY 排序列;
💡 提示

  • GROUP BY 中的列不需要和 SELECT 中的列顺序一致,但必须包含所有非聚合列
  • HAVING 中可以使用聚合函数的别名(MySQL、PostgreSQL支持),但标准SQL要求写完整表达式
  • NULL 值在 GROUP BY 中会被归为一组

📌 示例

示例:按部门统计员工信息

SQL
SELECT d.department_name,
       COUNT(e.employee_id) AS 人数,
       AVG(e.salary) AS 平均工资,
       SUM(e.salary) AS 工资总额
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
GROUP BY d.department_name
ORDER BY 工资总额 DESC;
▶ 试一试

输出:

TEXT
department_name  人数  平均工资    工资总额
---------------  ----  ---------  ----------
技术部           3     17666.67   53000.00
财务部           2     13500.00   27000.00
市场部           2     11500.00   23000.00
行政部           1     9000.00    9000.00
💡 解读:使用 LEFT JOIN 确保没有员工的部门也会显示(人数为0)。GROUP BY department_name 按部门名称分组。


示例:WHERE + GROUP BY + HAVING 组合

SQL
-- 2024年入职的员工中,找出平均工资>12000的部门
SELECT department_id,
       COUNT(*) AS 人数,
       AVG(salary) AS 平均工资
FROM employees
WHERE hire_date >= '2024-01-01'
GROUP BY department_id
HAVING AVG(salary) > 12000
ORDER BY 平均工资 DESC;
▶ 试一试

输出:

TEXT
department_id  人数  平均工资
-------------  ----  ---------
1              2     18500.00
3              1     14000.00
💡 执行过程

  1. WHERE hire_date >= '2024-01-01' — 先筛出2024年入职的员工
  2. GROUP BY department_id — 按部门分组
  3. HAVING AVG(salary) > 12000 — 只保留平均工资>12000的组
  4. ORDER BY 平均工资 DESC — 按平均工资降序排列

示例:多列分组 + 复杂条件

SQL
-- 每个部门、每种状态的员工统计,只看人数>=2的组
SELECT d.department_name,
       e.status,
       COUNT(*) AS 人数,
       MIN(e.salary) AS 最低工资,
       MAX(e.salary) AS 最高工资
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.salary > 0
GROUP BY d.department_name, e.status
HAVING COUNT(*) >= 2
ORDER BY d.department_name, 人数 DESC;
▶ 试一试

输出:

TEXT
department_name  status  人数  最低工资    最高工资
---------------  ------  ----  ---------  ----------
技术部           active  3     15000.00   20000.00
财务部           active  2     12000.00   14000.00
市场部           active  2     10000.00   12000.00
💡 解读:先用 WHERE 排除工资为0的行,再按部门+状态分组,HAVING 过滤掉人数不足2的组,最后排序。


🎬 场景演练

场景一:销售报表——按月统计订单

生成每月的销售报表,只显示订单数>=3的月份。

SQL
SELECT 
    EXTRACT(YEAR FROM order_date) AS 年份,
    EXTRACT(MONTH FROM order_date) AS 月份,
    COUNT(*) AS 订单数,
    SUM(total_amount) AS 总金额,
    AVG(total_amount) AS 平均订单金额
FROM orders
WHERE status IN ('completed', 'shipped')
GROUP BY EXTRACT(YEAR FROM order_date), EXTRACT(MONTH FROM order_date)
HAVING COUNT(*) >= 3
ORDER BY 年份, 月份;
💡 思路WHERE 过滤出已完成和已发货的订单,按年月分组统计,HAVING 过滤掉订单数不足3的月份。

场景二:HR分析——找出高薪部门

找出平均工资高于公司整体平均工资的部门。

SQL
SELECT d.department_name,
       COUNT(e.employee_id) AS 人数,
       AVG(e.salary) AS 部门平均工资
FROM departments d
JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_name
HAVING AVG(e.salary) > (SELECT AVG(salary) FROM employees)
ORDER BY 部门平均工资 DESC;
💡 思路HAVING 中可以使用子查询。先计算每个部门的平均工资,再与公司整体平均工资比较。这是聚合函数与子查询的结合应用。


❓ 常见问题

Q:SELECT中的列必须出现在GROUP BY中吗? A: 是的,所有非聚合列都必须出现在 GROUP BY 中。这是SQL标准的要求。例如 SELECT a, b, COUNT(*) FROM t GROUP BY a 会报错,因为 b 既不在 GROUP BY 中,也没有被聚合函数包裹。

Q:HAVING能用SELECT中的别名吗? A: MySQL和PostgreSQL支持在 HAVING 中使用别名,但SQL Server不支持。为了兼容性,建议写完整表达式:HAVING AVG(salary) > 10000 而不是 HAVING 平均工资 > 10000

Q:GROUP BY中的NULL值怎么处理? A: NULL 在分组时会被归为一组。如果 department_id 有NULL值,所有NULL的行会被分到同一组。这通常是有用的,但要注意区分"没有部门"和"部门ID为0"。

Q:WHERE和HAVING可以同时用吗? A: 可以,而且很常见。WHERE 在分组前过滤行,HAVING 在分组后过滤组。执行顺序:WHEREGROUP BYHAVING


📖 小节


📝 作业

作业1(⭐):查询每个部门的员工人数和平均工资,按平均工资从高到低排序。

作业2(⭐⭐):查询每个部门中2024年以后入职的员工人数,只显示人数>=2的部门。

作业3(⭐⭐⭐):查询每个部门的最高工资员工。要求显示部门名称、员工姓名和工资。提示:可以用子查询或 ROW_NUMBER() 窗口函数(后续学习)实现。


下一课

👉 15-advanced-functions - 高级函数:学习SQL高级函数,掌握字符串处理、数值计算、日期操作等实用技能!

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏