未记录事实核验 · 状态:现行内容
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适用于GROUP BY分组聚合、WHERE/HAVING及子查询分层的基础学习,示例需在具备相应表和字段的数据库中使用;日期函数主要采用MySQL语法,迁移至PostgreSQL或SQLite时需替换相关函数,SQLite默认不提供STDDEV。索引示例仅提供候选方案,实际效果需结合数据分布和执行计划判断,统计范围不同的查询不能直接比较性能;入职年份查询计算的是salary字段均值,仅当该字段记录入职薪资时才可解释为平均起薪。本次仅作全文结构审阅,未进行数据库执行测试或外部事实核验。
🧩 SQL GROUP BY分组操作
abstract 📋 本节概览
- 🎯 学习目标:掌握GROUP BY子句的语法和应用,实现数据分组聚合操作
- 📚 前置知识:聚合函数
- ⏱️ 预计用时:20分钟
- 🔧 开发环境:日期函数示例主要采用MySQL语法;PostgreSQL和SQLite需替换相应日期函数,SQLite默认不提供STDDEV;PostgreSQL部分索引示例单独标注
- 📊 数据集:users、orders等关系型表结构
🔧 技术定义
GROUP BY子句是SQL中用于数据分组的子句,它将查询结果按照指定的列值进行分组,相同值的记录将被归类到同一组中,然后对每个组执行聚合计算。
🛠️ 工作机制
执行顺序:
- 数据过滤:先执行WHERE子句进行记录过滤
- 数据分组:按GROUP BY指定的列将数据分为不同组
- 聚合计算:对每个组执行聚合函数计算
- HAVING过滤:对聚合结果进行过滤(如果有)
- 结果排序:按ORDER BY进行最终排序
核心特征:
- 结果集压缩:多行记录合并为一行组统计信息
- SELECT限制:为保持可移植性,非聚合表达式应由分组字段或常量构成;部分数据库还支持满足函数依赖条件的非分组列
- 组内计算:聚合函数仅对当前组内数据计算
📈 基础语法结构
📝 标准语法
SELECT
grouping_column(s),
aggregate_function(column)
FROM table_name
[WHERE condition]
GROUP BY grouping_column(s)
[HAVING aggregate_condition]
[ORDER BY column];
📁 单字段分组
-- 按城市统计用户数量
SELECT
city,
COUNT(*) as user_count
FROM users
GROUP BY city
ORDER BY user_count DESC;
-- 按部门统计平均薪资
SELECT
department,
AVG(salary) as avg_salary,
COUNT(*) as employee_count
FROM employees
GROUP BY department;
-- 按订单状态统计总金额
SELECT
order_status,
SUM(order_amount) as total_amount,
COUNT(*) as order_count
FROM orders
GROUP BY order_status;
📂 多字段分组
-- 按城市和年龄段分组统计
SELECT
city,
CASE
WHEN age < 25 THEN '青年'
WHEN age BETWEEN 25 AND 35 THEN '中青年'
ELSE '中年'
END as age_group,
COUNT(*) as user_count
FROM users
GROUP BY city, age_group
ORDER BY city, age_group;
-- 按年月和产品类别统计销售金额
SELECT
DATE_FORMAT(order_date, '%Y-%m') as order_month,
product_category,
SUM(order_amount) as monthly_sales,
COUNT(*) as order_count
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY order_month, product_category
ORDER BY order_month, product_category;
🔍 WHERE与GROUP BY结合使用
📋 数据过滤后分组
WHERE子句在GROUP BY之前执行,用于在分组前过滤原始数据:
-- 只统计成年用户的城市分布
SELECT
city,
COUNT(*) as adult_user_count
FROM users
WHERE age >= 18
GROUP BY city
HAVING COUNT(*) > 100 -- 只显示成年用户数超过100的城市
ORDER BY adult_user_count DESC;
-- 统计最近30天各产品类别的销售情况
SELECT
product_category,
COUNT(*) as order_count,
SUM(order_amount) as total_sales,
AVG(order_amount) as avg_order_value
FROM orders
WHERE order_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
AND order_status = 'completed'
GROUP BY product_category
ORDER BY total_sales DESC;
📊 HAVING子句过滤聚合结果
HAVING在GROUP BY之后执行,用于对聚合结果进行过滤:
-- 查找平均薪资超过万元的部门
SELECT
department,
AVG(salary) as avg_salary,
COUNT(*) as employee_count
FROM employees
GROUP BY department
HAVING AVG(salary) > 10000
ORDER BY avg_salary DESC;
-- 查找订单数量超过50的用户
SELECT
user_id,
COUNT(*) as order_count,
SUM(order_amount) as total_spent
FROM orders
WHERE order_status = 'completed'
GROUP BY user_id
HAVING COUNT(*) > 50
ORDER BY order_count DESC;
⚠️ 常见错误与规则
🚫 SELECT子句限制规则
在使用GROUP BY时,为保持可移植性,SELECT子句通常使用以下元素;部分数据库还允许满足函数依赖条件的非分组列:
- 分组字段:GROUP BY中指定的列
- 聚合函数:COUNT、SUM、AVG、MAX、MIN等
- 常量:字面值或表达式
-- ❌ 错误:非分组字段出现在SELECT中
SELECT department, employee_name, COUNT(*)
FROM employees
GROUP BY department; -- employee_name不在GROUP BY中
-- ✅ 正确:只选择分组字段和聚合函数
SELECT
department,
COUNT(*) as employee_count,
AVG(salary) as avg_salary
FROM employees
GROUP BY department;
-- ❌ 错误:在WHERE中使用聚合函数
SELECT department, COUNT(*)
FROM employees
WHERE COUNT(*) > 5 -- WHERE中不能使用聚合函数
GROUP BY department;
-- ✅ 正确:在HAVING中使用聚合函数
SELECT department, COUNT(*) as emp_count
FROM employees
GROUP BY department
HAVING COUNT(*) > 5;
🛠️ WHERE vs HAVING区别
| 特征 | WHERE | HAVING |
|---|---|---|
| 执行时机 | 分组前 | 分组后 |
| 作用对象 | 原始记录 | 聚合结果 |
| 可用表达式 | 原始记录字段及标量表达式,不可直接使用本层查询的聚合函数 | 分组字段、聚合函数及基于它们的表达式 |
| 性能影响 | 减少分组数据量 | 过滤聚合结果 |
-- 统计2024年各部门高薪员工(>8000)的情况
SELECT
department,
COUNT(*) as high_salary_count,
AVG(salary) as dept_avg_salary
FROM employees
WHERE hire_date >= '2024-01-01' -- 过滤原始记录
AND hire_date < '2025-01-01' -- 将入职日期限制在2024年
AND salary > 8000 -- 过滤原始记录
GROUP BY department
HAVING COUNT(*) >= 3 -- 过滤聚合结果
ORDER BY dept_avg_salary DESC;
🏢 业务实战案例
📊 电商数据分析
-- 月度销售报表
SELECT
DATE_FORMAT(order_date, '%Y-%m') as sales_month,
COUNT(*) as total_orders,
COUNT(DISTINCT user_id) as active_customers,
SUM(order_amount) as monthly_revenue,
AVG(order_amount) as avg_order_value
FROM orders
WHERE order_status = 'completed'
AND order_date >= '2024-01-01'
GROUP BY sales_month
ORDER BY sales_month;
-- 用户消费分层分析
SELECT
CASE
WHEN total_spent < 1000 THEN '低价值用户'
WHEN total_spent BETWEEN 1000 AND 5000 THEN '中价值用户'
ELSE '高价值用户'
END as customer_segment,
COUNT(*) as customer_count,
AVG(order_count) as avg_orders_per_customer,
SUM(total_spent) as segment_revenue
FROM (
SELECT
user_id,
COUNT(*) as order_count,
SUM(order_amount) as total_spent
FROM orders
WHERE order_status = 'completed'
GROUP BY user_id
) customer_stats
GROUP BY customer_segment
ORDER BY segment_revenue DESC;
🏭 人力资源统计
-- 部门薪酬结构分析
SELECT
department,
COUNT(*) as employee_count,
MIN(salary) as min_salary,
MAX(salary) as max_salary,
AVG(salary) as avg_salary,
STDDEV(salary) as salary_stddev, -- 薪资标准差
SUM(salary) as total_payroll
FROM employees
WHERE status = 'active'
GROUP BY department
HAVING COUNT(*) >= 5 -- 只统计人数≥ 5的部门
ORDER BY avg_salary DESC;
-- 入职年份与薪资关系分析
SELECT
YEAR(hire_date) as hire_year,
department,
COUNT(*) as hired_count,
AVG(salary) as avg_salary -- 仅当salary记录入职薪资时,才能解释为平均起薪
FROM employees
WHERE hire_date >= '2020-01-01'
GROUP BY hire_year, department
HAVING COUNT(*) >= 3
ORDER BY hire_year DESC, department;
⚡ 性能优化要点
🚀 GROUP BY性能优化
- 索引优化:为GROUP BY字段创建适当索引
- 分组字段顺序:结合索引列顺序、过滤条件和执行计划评估,不存在按基数从小到大排列就普遍更快的规则
- WHERE过滤优先:先用WHERE减少数据量再分组
- 选择性投影:只选择需要的列,避免SELECT *
-- 优化前:缺少索引,数据量大
SELECT department, COUNT(*)
FROM employees
GROUP BY department;
-- 另一统计需求示例:创建候选索引,仅统计在职员工;与上方全体员工统计范围不同,不能直接作为等价查询的性能对比
CREATE INDEX idx_employees_dept_status ON employees(department, status);
SELECT
department,
COUNT(*) as active_count
FROM employees
WHERE status = 'active' -- 先过滤再分组
GROUP BY department
ORDER BY department; -- 是否利用索引顺序避免额外排序,需检查执行计划
📊 存储引擎针对性优化
-- MySQL: 使用覆盖索引优化
CREATE INDEX idx_orders_status_date_amount
ON orders(order_status, order_date, order_amount);
-- PostgreSQL: 利用部分索引
CREATE INDEX CONCURRENTLY idx_orders_completed
ON orders(order_date, order_amount)
WHERE order_status = 'completed';
✅ 技能检查清单
基础技能认证:
- [ ] 理解GROUP BY的工作原理和执行顺序
- [ ] 掌握单字段和多字段分组语法
- [ ] 了解SELECT子句在GROUP BY中的限制规则
- [ ] 熟练使用WHERE和HAVING子句的区别
- [ ] 能够结合聚合函数进行数据统计分析
- [ ] 掌握ORDER BY与GROUP BY的组合使用
进阶技能:
- [ ] 能够设计复杂的分组统计查询
- [ ] 理解GROUP BY的性能影响和优化方法
- [ ] 掌握子查询与GROUP BY的结合使用
- [ ] 熟练解决实际业务中的数据分析问题
下一步学习:ORDER BY排序 - 学习结果集排序和性能优化