SQL分组查询精讲:GROUP BY与HAVING核心用法与实战

发布时间:2026/9/7 11:21:20
SQL分组查询精讲:GROUP BY与HAVING核心用法与实战 做数据统计时最常用的 SQL 需求就是把大量明细记录按某个维度汇总。比如“统计每个部门的人数”“分析每个月的订单金额”“找出下单次数超过 5 次的客户”。这些场景如果只靠WHERE过滤写起来很别扭甚至根本写不出来。真正高效的做法是使用GROUP BY分组再用HAVING过滤分组结果。这篇文章围绕GROUP BY和HAVING两个子句展开从基础概念、执行顺序、语法示例到完整的实战案例和常见高频报错一次性讲清楚。无论你是准备面试的在校学生还是刚接触 SQL 的开发新人都能照着练习。我自己在学习和项目开发中也经常看到有人把WHERE和HAVING混用或者在GROUP BY下写了不符合规范的SELECT列最后报一堆语法错误。所以说这两个子句看似简单背后需要注意的细节非常多。本文就结合经典教学思路和实际工程经验做一次系统整理。1. 背景与核心概念1.1 为什么需要 GROUP BY在没有GROUP BY的时候SQL 处理的是“一行一行”的数据。比如SELECT salary FROM employee;返回的是每条员工记录对应的薪水。但现实需求很少是“把每行都读出来看一眼”更多是“算一下总数”“取一下平均值”“找最大值”这类汇总操作。聚合函数COUNT、SUM、AVG、MAX、MIN可以完成单表整体汇总比如SELECT AVG(salary) FROM employee;这条语句会把整张表当成一组计算全体员工的平均工资。可一旦需求变成“每个部门的平均工资”“每个月的新增人数”单表整体汇总就不够用了。此时必须把数据先分成若干组再对每一组分别做聚合计算。GROUP BY做的事情就是按照一个或多个列的值把表数据拆分成多个分组然后在每个分组上应用聚合函数。举个例子employee表中有部门字段department那么SELECT department, COUNT(*) FROM employee GROUP BY department;执行过程可以理解为三步扫描employee表。按department字段的值把记录分成多个组相同部门的记录进入同一组。对每一组执行COUNT(*)得到该部门的人数。最终返回几行取决于表里有几个不同的部门。1.2 HAVING 解决了什么问题WHERE在分组之前过滤行这是很多人都知道的事情。但分组之后如果想对“组”做过滤该怎么办比如“查询平均工资大于 8000 的部门”。条件针对的是AVG(salary)而AVG(salary)是分组之后才计算出来的聚合结果。此时WHERE根本没法写因为在分组之前数据库还不知道每个组的平均值是多少。这时候就需要HAVINGSELECT department, AVG(salary) FROM employee GROUP BY department HAVING AVG(salary) 8000;HAVING和WHERE最核心的区别是WHERE在分组前过滤行不能使用聚合函数。HAVING在分组后过滤组可以使用聚合函数。可以这么记忆WHERE管“行”HAVING管“组”。1.3 完整执行顺序理解 SQL 执行顺序是搞清楚GROUP BY和HAVING的关键。很多人以为 SQL 是从SELECT开始读的实际上数据库引擎并不是这样执行。一条典型分组查询的完整逻辑顺序如下FROM确定数据来源表。WHERE对原始行逐行过滤。GROUP BY对过滤后的行进行分组。HAVING对分组后的结果进行过滤。SELECT计算要返回的列和聚合表达式。ORDER BY对最终结果排序。LIMIT截取指定行数。“先FROM再WHERE然后GROUP BY之后HAVING最后SELECT输出”这个顺序非常重要。因为它解释了为什么WHERE不能使用聚合函数当WHERE执行时分组还没有发生聚合值根本不存在。也解释了为什么HAVING中可以使用聚合函数它执行在分组之后。1.4 GROUP BY 与 DISTINCT 的区别有读者可能会问GROUP BY不是也能去重吗它和DISTINCT有什么不一样从结果上看SELECT DISTINCT department FROM employee;和SELECT department FROM employee GROUP BY department;返回的分组名称确实一样。但本质不同DISTINCT只是去掉重复行不涉及聚合计算。GROUP BY是为聚合计算服务的它把数据分好组等待COUNT、SUM等函数进一步处理。如果只想去重用DISTINCT更轻量如果要做汇总统计必须用GROUP BY。2. 环境准备与版本说明2.1 可选数据库系统GROUP BY和HAVING是 SQL 标准中的核心语法几乎所有主流关系型数据库都支持包括MySQLPostgreSQLSQL ServerOracleSQLite本文示例以 MySQL 8.x 为主要演示环境。使用其他数据库的读者也基本可以照抄只有个别细微差异比如字符串函数、日期函数的名称可能不同。遇到差异时我会在“常见问题与排查思路”一节单独说明。2.2 建库建表演示为了方便后续实战先创建一个演示数据库和表。如果你本机还没有安装数据库可以使用 Docker 快速启动一个 MySQL 实例docker run --name mysql-demo \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEcompany \ -p 3306:3306 \ -d mysql:8.0如果你已经安装了 MySQL、Navicat 或命令行客户端也可以直接使用图形界面执行 SQL。登录数据库后执行下面的建表语句CREATE DATABASE IF NOT EXISTS company; USE company; CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, department VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, salary DECIMAL(10, 2) NOT NULL, hire_date DATE NOT NULL );插入一批测试数据INSERT INTO employee (name, department, city, salary, hire_date) VALUES (张三, 技术部, 北京, 12000, 2022-03-15), (李四, 技术部, 北京, 15000, 2021-07-01), (王五, 技术部, 上海, 11000, 2023-01-10), (赵六, 产品部, 北京, 10000, 2020-11-23), (钱七, 产品部, 上海, 9000, 2022-06-30), (孙八, 产品部, 广州, 9500, 2023-05-12), (周九, 设计部, 深圳, 8000, 2021-09-18), (吴十, 设计部, 杭州, 8500, 2022-12-05), (郑十一, 测试部, 北京, 7500, 2023-08-01), (王十二, 测试部, 上海, 7800, 2020-04-16);确认一下数据是否插入成功SELECT * FROM employee;在后续小节中我会基于这张表逐步演示GROUP BY和HAVING的各种写法。3. 核心语法与执行原理3.1 GROUP BY 基础语法GROUP BY出现在WHERE之后HAVING与ORDER BY之前。基本语法结构如下SELECT 分组列, 聚合函数(计算列) FROM 表名 [WHERE 过滤条件] GROUP BY 分组列 [HAVING 分组过滤条件] [ORDER BY 排序列];方括号表示可选。关键规则SELECT中出现的非聚合列必须全部出现在GROUP BY中。例如下面的写法是合法的SELECT department, COUNT(*) FROM employee GROUP BY department;因为department同时出现在GROUP BY中。而下面的写法在 MySQL 8.0 默认的ONLY_FULL_GROUP_BY模式下会直接报错-- 错误示例name 没有出现在 GROUP BY 中 SELECT department, name, COUNT(*) FROM employee GROUP BY department;错误提示类似于Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column company.employee.name which is not functionally dependent on columns in GROUP BY clause原因很简单按部门分组后每个部门里有多名员工。数据库不知道该返回哪个name。除非你明确使用ANY_VALUE(name)、MAX(name)这种聚合函数否则 SQL 引擎拒绝猜测。3.2 常用的聚合函数GROUP BY通常与聚合函数搭配使用。下面列出最常用的五个聚合函数作用示例COUNT(*)统计组内行数COUNT(*)COUNT(列名)统计组内某列非 NULL 的数量COUNT(city)SUM(列名)组内求和SUM(salary)AVG(列名)组内求平均值AVG(salary)MAX(列名)组内最大值MAX(salary)MIN(列名)组内最小值MIN(salary)注意COUNT(*)和COUNT(具体列)的区别COUNT(*)统计的是行数不管某列是否为 NULLCOUNT(具体列)会忽略 NULL 值。3.3 HAVING 基础语法HAVING写在GROUP BY之后用于过滤分组。SELECT department, AVG(salary) AS avg_salary FROM employee GROUP BY department HAVING AVG(salary) 9000;这条 SQL 的含义是按department分组。计算每组的平均薪水。只保留平均薪水大于 9000 的部门。结果只会返回“技术部”和“产品部”因为设计部和测试部的平均工资不足 9000。HAVING和WHERE常常被写错。看下面这个错误示例-- 错误示例WHERE 中不能使用聚合函数 SELECT department FROM employee WHERE AVG(salary) 9000 GROUP BY department;这条语句在 MySQL、SQL Server、PostgreSQL 里都会报错因为WHERE是在分组之前执行的此时AVG(salary)还不存在。另外HAVING可以引用SELECT中的别名吗这取决于数据库。MySQL 支持在HAVING中使用别名例如SELECT department, AVG(salary) AS avg_salary FROM employee GROUP BY department HAVING avg_salary 9000;但为了可移植性建议直接在HAVING中写完整表达式AVG(salary) 9000避免在部分数据库中出现兼容问题。3.4 多列分组实际业务中按单列分组的场景比较有限。更常见的是“按多个维度组合汇总”。比如统计“每个城市每个部门的平均薪资”。SELECT city, department, AVG(salary) AS avg_salary FROM employee GROUP BY city, department ORDER BY city, department;执行逻辑是先按city分组然后在相同城市内再按department细分。例如“北京-技术部”和“上海-技术部”是两个不同的分组。多列分组时GROUP BY后面的列顺序只影响结果集默认的展示顺序不影响分组的最终结果。不过为了可读性建议和SELECT中的列顺序保持一致。3.5 GROUP BY 与 ORDER BY 组合分组统计通常配合排序使用。例如按平均工资从高到低列出各部门SELECT department, AVG(salary) AS avg_salary FROM employee GROUP BY department ORDER BY avg_salary DESC;这里ORDER BY可以用别名avg_salary也可以用聚合表达式AVG(salary)。也可以使用ORDER BY的排序列序号不过可读性较差不建议在正式代码中使用SELECT department, AVG(salary) FROM employee GROUP BY department ORDER BY 2 DESC;ORDER BY 2表示按第 2 列排序。这种写法适合临时排查数据不适合维护场景。4. 完整实战案例前面已经讲了语法和原理下面用一个贴近电商业务场景的完整案例把GROUP BY、HAVING、WHERE、ORDER BY全部串起来。假设有一个简化版的电商数据库包含customer客户表、orders订单表、order_item订单明细表。请先执行下面的建表和插入语句。USE company; CREATE TABLE customer ( id INT PRIMARY KEY AUTO_INCREMENT, customer_name VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL ); CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, customer_id INT NOT NULL, order_date DATE NOT NULL, total_amount DECIMAL(10, 2) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 已完成 ); CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_name VARCHAR(100) NOT NULL, category VARCHAR(50) NOT NULL, quantity INT NOT NULL, unit_price DECIMAL(10, 2) NOT NULL );插入模拟数据INSERT INTO customer (customer_name, city) VALUES (小明, 北京), (小红, 上海), (小刚, 广州); INSERT INTO orders (customer_id, order_date, total_amount, status) VALUES (1, 2024-01-05, 1200.00, 已完成), (1, 2024-01-18, 800.00, 已完成), (2, 2024-02-10, 2000.00, 已完成), (2, 2024-02-22, 450.00, 已取消), (3, 2024-03-02, 3200.00, 已完成), (3, 2024-03-15, 680.00, 已取消); INSERT INTO order_item (order_id, product_name, category, quantity, unit_price) VALUES (1, 手机, 数码, 1, 1000.00), (1, 手机壳, 配件, 2, 100.00), (2, 键盘, 外设, 1, 800.00), (3, 显示器, 数码, 1, 2000.00), (4, 鼠标垫, 外设, 3, 150.00), (5, 笔记本, 数码, 1, 3000.00), (5, 扩展坞, 配件, 1, 200.00), (6, 耳机, 数码, 1, 680.00);4.1 统计每个客户的订单总金额需求只统计已完成订单算出每个客户的总下单金额并按金额从高到低排序。SELECT c.customer_name, SUM(o.total_amount) AS total_spent FROM customer c JOIN orders o ON c.id o.customer_id WHERE o.status 已完成 GROUP BY c.id, c.customer_name ORDER BY total_spent DESC;这里要注意WHERE o.status 已完成在分组前过滤掉了已取消的订单只保留已完成订单参与汇总。预期结果customer_nametotal_spent小刚3200.00小明2000.00小红2000.004.2 统计每个商品类别的销量和销售额需求统计每个商品类别的总销售件数和总销售额。SELECT category, SUM(quantity) AS total_quantity, SUM(quantity * unit_price) AS total_sales FROM order_item GROUP BY category ORDER BY total_sales DESC;这里使用SUM(quantity * unit_price)计算销售额GROUP BY category按类别分组。预期结果categorytotal_quantitytotal_sales数码33680.00外设4950.00配件3400.004.3 找出消费总金额超过 2000 的客户需求提取已完成订单按客户分组计算总金额只保留消费超过 2000 的客户。SELECT c.customer_name, SUM(o.total_amount) AS total_spent FROM customer c JOIN orders o ON c.id o.customer_id WHERE o.status 已完成 GROUP BY c.id, c.customer_name HAVING total_spent 2000;这里的HAVING total_spent 2000使用了别名。MySQL 支持这种写法如果是其他数据库可以改成HAVING SUM(o.total_amount) 2000。预期结果只有一行小刚 3200.00。4.4 多维度分组统计每个城市每个类别的销量需求把客户所在城市和商品类别进行组合统计每个组合的总销量和总销售额。SELECT c.city, oi.category, SUM(oi.quantity) AS total_quantity, SUM(oi.quantity * oi.unit_price) AS total_sales FROM customer c JOIN orders o ON c.id o.customer_id JOIN order_item oi ON o.id oi.order_id WHERE o.status 已完成 GROUP BY c.city, oi.category ORDER BY c.city, oi.category;这个例子展示了多表关联、多列分组、聚合计算的综合用法。注意GROUP BY中出现的非聚合列在SELECT中也必须出现。4.5 使用 EXPLAIN 分析分组查询在真实项目中GROUP BY往往是慢查询的常见来源之一。遇到查询变慢时可以用EXPLAIN查看执行计划。EXPLAIN SELECT category, SUM(quantity) FROM order_item GROUP BY category;执行结果中的type列如果显示ALL说明走了全表扫描。当order_item表数据量很大时可以为category建立索引CREATE INDEX idx_order_item_category ON order_item(category);添加索引后重新执行EXPLAIN可以看到访问类型可能变为index扫描效率提升。不过索引也不是万能的具体是否有效需要结合数据量、数据分布和真实执行计划综合判断。4.6 Python 调用分组查询工程中SQL 往往不是孤立存在的。以 Python 为例可以使用pymysql执行分组查询并把结果转成字典列表方便后续数据处理。# -*- coding: utf-8 -*- import pymysql conn pymysql.connect( host127.0.0.1, port3306, userroot, password123456, databasecompany, charsetutf8mb4 ) sql SELECT department, COUNT(*) AS emp_count, AVG(salary) AS avg_salary FROM employee GROUP BY department HAVING COUNT(*) 2 ORDER BY avg_salary DESC with conn.cursor() as cursor: cursor.execute(sql) rows cursor.fetchall() columns [desc[0] for desc in cursor.description] result [dict(zip(columns, row)) for row in rows] for item in result: print(item) conn.close()这个脚本执行了一个带GROUP BY、HAVING、ORDER BY的完整查询并把每一行结果转成了字典方便后续拼接 JSON 或写入报表。需要提醒的是实际项目中使用 SQL 时如果查询条件包含用户输入内容一定要使用参数化查询或预编译语句不能直接拼接字符串否则容易产生 SQL 注入风险。比如上面的查询如果带有WHERE应该写成sql SELECT department, COUNT(*) AS emp_count FROM employee WHERE city %s GROUP BY department cursor.execute(sql, (北京,))这是工程中必须养成的习惯。5. 常见问题与排查思路5.1 常见报错与解决方案问题现象常见原因解决思路... is not in GROUP BY clauseSELECT 中的非聚合列没有全部出现在 GROUP BY 中把该列加入 GROUP BY或改用聚合函数包裹Invalid use of group function在 WHERE 中使用了聚合函数把条件改到 HAVING 中或用子查询先聚合再过滤HAVING 条件不生效条件写在了 WHERE 中导致分组前被错误过滤检查过滤逻辑行级过滤用 WHERE分组级过滤用 HAVING分组结果顺序混乱没有写 ORDER BY显式使用 ORDER BY 排序返回行数比预期多GROUP BY多列组合后分组粒度变细检查分组列是否正确多列分组会生成更多组合使用别名报错部分数据库不支持在 WHERE 或 HAVING 中引用 SELECT 别名在 HAVING 中写完整聚合表达式5.2 为什么我的 SELECT 列报错这是初学者最容易遇到的问题。-- 错误写法 SELECT department, name, COUNT(*) FROM employee GROUP BY department;原因是分组之后每个部门可能包含多个员工name字段不知道该取谁。MySQL 5.7.5 之后默认开启ONLY_FULL_GROUP_BY模式会直接拒绝这种不严谨的写法。解决方案有三种把name加入GROUP BY表示按“部门和姓名”这个更细的维度分组。使用MAX(name)或MIN(name)聚合函数。使用ANY_VALUE(name)表示“组内任意一个值”。SELECT department, ANY_VALUE(name), COUNT(*) FROM employee GROUP BY department;在项目中我更推荐第一种想清楚你到底按什么维度汇总然后严格要求SELECT与GROUP BY对齐。5.3 NULL 值会如何分组GROUP BY会把所有 NULL 值分到同一组。比如city列存在 NULL 时所有 NULL 城市会归为一组分组名称显示为 NULL。如果不想让 NULL 参与分组可以使用COALESCE函数提前转换SELECT COALESCE(city, 未知城市) AS city_name, COUNT(*) FROM employee GROUP BY COALESCE(city, 未知城市);5.4 为什么 HAVING 过滤后数据变少HAVING是分组之后的过滤它删除的是整个分组而不是组内的某一行。举个例子SELECT department, COUNT(*) FROM employee GROUP BY department HAVING COUNT(*) 3;如果某部门只有 2 名员工该部门整行不会出现在结果中。这不是 BUG而是HAVING的执行逻辑。想要保留所有部门只是隐藏人数少的员工那属于行级过滤应该用WHERE处理而不是HAVING。5.5 SQL Server 中的特殊限制如果使用 SQL Server需要注意老版本中GROUP BY不能直接对text、ntext、image类型使用。SQL Server 对HAVING中使用别名的支持与 MySQL 不同。使用datetime类型分组时建议先转换成日期格式避免时间部分影响分组结果。例如 SQL Server 中按“年-月”分组可以这样写SELECT FORMAT(order_date, yyyy-MM) AS order_month, SUM(total_amount) AS total_amount FROM orders GROUP BY FORMAT(order_date, yyyy-MM);MySQL 中对应的写法是DATE_FORMAT(order_date, %Y-%m)。不同数据库的日期函数差异较大跨库迁移时要特别留意。5.6 慢 SQL 排查思路GROUP BY导致慢查询的最常见原因是分组列没有索引数据库需要建立临时表或执行 filesort。排查顺序使用EXPLAIN查看执行计划。观察type、key、Extra列。如果Extra中出现Using temporary或Using filesort说明分组或排序没有充分利用索引。尝试为分组列和排序列建立联合索引。例如下面的查询SELECT city, department, COUNT(*) FROM employee GROUP BY city, department;可以建立一个联合索引CREATE INDEX idx_city_department ON employee(city, department);索引列的顺序要和GROUP BY的顺序一致这样数据库可以更高效地扫描分组边界减少临时表的创建。6. 最佳实践与工程建议6.1 明确区分 WHERE 和 HAVING这是最重要的一条工程准则WHERE过滤的是基表行不能使用聚合函数。HAVING过滤的是分组结果可以使用聚合函数。写 SQL 之前先想清楚你的过滤目标是“行”还是“组”。目标选错结果一定错。团队代码评审时我也经常提醒同事能用WHERE提前过滤的数据不要等着HAVING过滤。因为WHERE在分组前执行可以减少进入分组的行数降低分组计算的开销。对比下面两条 SQL-- 写法一先过滤再分组 SELECT department, COUNT(*) FROM employee WHERE salary 8000 GROUP BY department; -- 写法二先分组再过滤组内记录 SELECT department, COUNT(*) FROM employee GROUP BY department HAVING AVG(salary) 8000;写法一统计的是“每个部门中薪资大于 8000 的人数”。写法二统计的是“平均薪资大于 8000 的部门”。两者结果完全不同千万不要混用。6.2 始终使用完整分组列SELECT中出现的每个非聚合列都必须加入GROUP BY。这是 SQL 标准行为。虽然在 MySQL 的宽松模式下不写全可能也能执行但这属于不确定行为。一旦数据变化结果可能完全不可预期。正确示例SELECT department, city, COUNT(*) FROM employee GROUP BY department, city;错误示例-- department 和 city 都是非聚合列却只对 department 分组 SELECT department, city, COUNT(*) FROM employee GROUP BY department;把SELECT和GROUP BY对齐是避免隐性错误最有效的手段。6.3 对分组结果排序时显式使用 ORDER BY很多人以为GROUP BY会自动排序。在旧版 MySQL 中确实如此但这不是标准 SQL 行为。MySQL 8.0 之后GROUP BY不再默认排序。如果业务依赖排序结果必须显式写ORDER BY。SELECT department, AVG(salary) AS avg_salary FROM employee GROUP BY department ORDER BY avg_salary DESC;6.4 小心 ONLY_FULL_GROUP_BY 模式MySQL 5.7.5 及以上版本默认启用sql_mode ONLY_FULL_GROUP_BY。建议保持开启不要为了省事把它关掉。这个模式能挡住很多潜在的逻辑错误。检查当前模式SELECT sql_mode;如果确实因为历史原因关闭了该模式也建议在开发环境中重新开启让不规范 SQL 在测试阶段就暴露出来。6.5 大数据量下的查询优化当表中数据量很大时GROUP BY可能会成为性能瓶颈。下面几个优化方向值得记住减少参与分组的数据量尽可能用WHERE提前过滤。为分组列建立合适索引联合索引的列顺序要和GROUP BY顺序一致。避免对函数计算结果分组如GROUP BY MONTH(order_date)这种写法无法有效利用索引。可以提前生成一个冗余的月份字段。使用覆盖索引让索引同时覆盖分组列和聚合列减少回表。考虑窗口函数如果需求是“保留明细行的同时显示聚合值”可以使用SUM() OVER(PARTITION BY ...)代替子查询和自连接。比如希望查询每个员工的信息同时显示他所在部门的平均工资用窗口函数更合适SELECT name, department, salary, AVG(salary) OVER(PARTITION BY department) AS dept_avg_salary FROM employee;这种写法不会把多行合并成一行又能带出分组聚合信息在报表场景中非常实用。6.6 注意 SQL 注入风险GROUP BY本身不涉及安全问题但动态拼接 SQL 时如果用户输入被直接拼进GROUP BY或HAVING条件可能造成注入风险。例如# 错误示例group_col 来自用户输入 sql fSELECT {group_col}, COUNT(*) FROM employee GROUP BY {group_col}正确的做法是使用白名单校验allowed_columns [department, city, name] if group_col not in allowed_columns: raise ValueError(非法的分组字段)在生产环境中任何涉及外部输入的 SQL 都要坚持白名单或预编译不能心存侥幸。6.7 生产环境变更前必须测试无论是对线上表结构调整还是对大表执行批量分组统计都建议先在测试环境验证并把数据备份好。特别是涉及DELETE、UPDATE和大量数据聚合时要评估对数据库性能的影响避免长时间锁表影响线上业务。7. 总结与学习路线本文从GROUP BY和HAVING的概念入手逐步拆解了执行顺序、语法规则、多列分组、聚合函数组合以及一个相对完整的电商业务实战案例。同时整理了常见的语法报错、NULL 值分组、慢查询排查思路和工程层面的最佳实践。学完这一篇你应该能回答以下几个问题GROUP BY的作用是什么它和DISTINCT有什么本质区别WHERE和HAVING的执行顺序是怎样的为什么WHERE不能用聚合函数SELECT中出现非聚合列时必须满足什么条件分组查询性能变慢时第一步排查动作是什么动态拼接GROUP BY字段时如何避免 SQL 注入接下来可以继续学习这些方向子查询在GROUP BY结果上做二次查询。JOIN多表关联后再分组这是报表开发最常用的组合。窗口函数ROW_NUMBER()、RANK()、SUM() OVER(PARTITION BY ...)适合复杂排名和占比统计。慢 SQL 优化通过EXPLAIN分析执行计划理解索引和临时表。视图与存储过程把常用分组统计封装成数据库对象简化业务层代码。建议你立刻打开数据库客户端把本文中的示例建表和查询语句执行一遍。不要只看不练GROUP BY的细节只有在亲手写过、报过错、排查过之后才会真正变成你自己的经验。如果本文对你有帮助可以收藏备用。遇到GROUP BY相关的报错和排查场景时随时回来对照查一查。