MySQL 聚合函数、分组、联合查询详解

 更新时间:2026年01月23日 09:24:06   作者:bbq粉刷匠  
本文介绍了MySQL中的聚合函数、分组查询和联合查询,并通过多个示例题目进行巩固,聚合函数用于计算汇总统计值,分组查询用于将数据分成组并应用聚合函数,联合查询用于从多个表中组合数据,通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

本篇来讲解 MySQL 中的聚合函数、分组查询 (GROUP BY) 以及联合查询 (JOIN),并配合大量题目进行巩固~~

第一部分:聚合函数 (Aggregate Functions)

聚合函数对一组值执行计算,并返回单个值。它们通常与 GROUP BY 子句一起使用,用于对分组后的数据进行汇总统计。MySQL 提供了多种聚合函数:

  • COUNT(): 计算行数。
    • COUNT(*): 计算所有行的数量,包括包含 NULL 值的行。
    • COUNT(column_name): 计算指定列中非 NULL 值的数量。
    • COUNT(DISTINCT column_name): 计算指定列中不同(去重)非 NULL 值的数量。
  • 示例题目 1.1: 假设有一个 students 表 (id, name, major, gpa)。查询表中总共有多少学生。
  • SELECT COUNT(*) AS total_students FROM students;
    
  • 示例题目 1.2: 查询有多少个不同的专业 (major) (假设 major 可能重复)。
  • SELECT COUNT(DISTINCT major) AS distinct_majors FROM students;
    
  • SUM(): 计算数值列的总和。忽略 NULL 值。
  • 示例题目 1.3: 查询所有学生的总 gpa 之和(假设 gpa 是数值类型)。
  • SELECT SUM(gpa) AS total_gpa FROM students;
    
  • AVG(): 计算数值列的平均值。忽略 NULL 值。
  • 计算平均值 $ \bar{x} $ 的公式为: $$ \bar{x} = \frac{\sum_{i=1}^{n} x_i}{n} $$
  • 示例题目 1.4: 查询所有学生的平均 gpa
  • SELECT AVG(gpa) AS average_gpa FROM students;
    
  • MAX(): 返回指定列中的最大值。可用于数值、日期和字符串(按字符顺序)。忽略 NULL 值。
  • 示例题目 1.5: 查询最高的 gpa 是多少。
  • SELECT MAX(gpa) AS highest_gpa FROM students;
    
  • MIN(): 返回指定列中的最小值。可用于数值、日期和字符串(按字符顺序)。忽略 NULL 值。
  • 示例题目 1.6: 查询最低的 gpa 是多少。
  • SELECT MIN(gpa) AS lowest_gpa FROM students;
    
    • GROUP_CONCAT() (MySQL 特有): 将来自一个组的多个行中的字符串值连接成一个字符串。可以指定分隔符。
    • GROUP_CONCAT(column_name): 默认用逗号 , 连接。
    • GROUP_CONCAT(column_name SEPARATOR ';'): 用分号 ; 连接。
    • GROUP_CONCAT(DISTINCT column_name ORDER BY column_name): 去重并按指定列排序后连接。
  • 示例题目 1.7: 按专业 (major) 分组,列出每个专业的所有学生姓名(用逗号分隔)。
  • SELECT major, GROUP_CONCAT(name) AS student_names
    FROM students
    GROUP BY major;
    
  • 示例题目 1.8: 按专业分组,列出每个专业所有 不同 的学生姓名(按字母顺序排序,用分号分隔)。
SELECT major, GROUP_CONCAT(DISTINCT name ORDER BY name SEPARATOR ';') AS distinct_sorted_names
FROM students
GROUP BY major;

关键点总结 (聚合函数):

  • 聚合函数作用于 一组 行,返回一个 单一 的值。
  • 它们通常用于 SELECT 列表中。
  • 它们会忽略 NULL 值(COUNT(*) 除外)。
  • DISTINCT 可以在聚合函数内部使用(如 COUNT(DISTINCT major))来对列值去重后再计算。
  • 单独使用聚合函数时,它们作用于整个查询结果集。

第二部分:分组查询 (GROUP BY)

  • GROUP BY 子句用于根据一个或多个列对结果集进行分组。然后,在每个分组上应用聚合函数,得到每个组的汇总信息。

基本语法:

SELECT column1, aggregate_function(column2)
FROM table_name
WHERE condition -- 可选,在分组 *前* 过滤行
GROUP BY column1 -- 指定分组依据的列
HAVING condition; -- 可选,在分组 *后* 过滤分组

执行顺序:

  • FROM: 确定数据来源。
  • WHERE: 根据条件过滤 行 (在分组前)。
  • GROUP BY: 将剩余的行分成组。
  • HAVING: 根据条件过滤 组 (在分组后,通常涉及聚合函数)。
  • SELECT: 选择要显示的列(包括分组列和聚合结果)。
  • ORDER BY: 对最终结果排序。

分组依据:

  • 可以按单列分组:GROUP BY department_id
  • 可以按多列分组:GROUP BY department_id, job_title (先按 department_id 分大组,再在每个大组内按 job_title 分小组)。

示例题目 2.1: 有一个 orders 表 (order_id, customer_id, product_id, quantity, order_date)。统计每个客户 (customer_id) 总共下了多少订单 (COUNT(order_id))。

SELECT customer_id, COUNT(order_id) AS order_count
FROM orders
GROUP BY customer_id;
  • HAVING vs WHERE:
    • WHERE: 在分组 前 过滤 行。不能使用聚合函数。
    • HAVING: 在分组 后 过滤 组。可以使用聚合函数。

示例题目 2.2: 查询平均成绩 (AVG(gpa)) 大于 3.5 的专业 (major)。

SELECT major, AVG(gpa) AS avg_gpa
FROM students
GROUP BY major
HAVING AVG(gpa) > 3.5; -- HAVING 过滤分组

示例题目 2.3: 查询订单表中,在 2023 年 (WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31') 下单次数超过 5 次 (COUNT(order_id) > 5) 的客户 (customer_id)。

SELECT customer_id, COUNT(order_id) AS order_count
FROM orders
WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31' -- WHERE 在分组前过滤行
GROUP BY customer_id
HAVING COUNT(order_id) > 5; -- HAVING 在分组后过滤组
  • GROUP BY 与表达式: 分组依据可以是表达式的结果。

示例题目 2.4: 按年份 (YEAR(order_date)) 统计每年的总销售额 (SUM(quantity * unit_price),假设 unit_price 存在)。

SELECT YEAR(order_date) AS order_year, SUM(quantity * unit_price) AS total_sales
FROM orders
JOIN products ON orders.product_id = products.product_id -- 假设有 products 表
GROUP BY YEAR(order_date);

键点总结 (GROUP BY):

  • GROUP BY 用于将数据分成逻辑组。
  • 出现在 SELECT 列表中非聚合函数列,通常 必须 出现在 GROUP BY 子句中(或者被包含在聚合函数中)。
  • 使用 HAVING 对分组结果进行筛选,通常涉及聚合函数。
  • 理解 WHERE (行过滤) 和 HAVING (组过滤) 的执行时机和区别至关重要。

第三部分:联合查询 (JOIN)

联合查询用于从两个或多个表(或视图)中基于它们之间的相关列组合行。这是关系型数据库的核心操作,用于整合分散在不同表中的数据。

MySQL 支持多种 JOIN 类型:

INNER JOIN (或简写为 JOIN):

  • 返回两个表中满足连接条件的所有行。如果某行在其中一个表中没有匹配的行,则该行不会出现在结果中。

语法:

SELECT columns
FROM table1
[INNER] JOIN table2
ON table1.common_column = table2.common_column;

示例题目 3.1: 有两个表:employees (emp_id, emp_name, dept_id) 和 departments (dept_id, dept_name)。查询所有员工及其所在部门的名称。

SELECT e.emp_name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id;

LEFT JOIN (或 LEFT OUTER JOIN):

  • 返回左表 (table1) 的所有行,即使在右表 (table2) 中没有匹配的行。如果右表中没有匹配的行,则结果中右表的部分将包含 NULL

语法:

SELECT columns
FROM table1
LEFT [OUTER] JOIN table2
ON table1.common_column = table2.common_column;

示例题目 3.2: 查询所有部门 (departments),并列出属于该部门的员工姓名(如果部门没有员工,员工姓名显示为 NULL)。

SELECT d.dept_name, e.emp_name
FROM departments d
LEFT JOIN employees e ON d.dept_id = e.dept_id;

RIGHT JOIN (或 RIGHT OUTER JOIN):

  • 返回右表 (table2) 的所有行,即使在左表 (table1) 中没有匹配的行。如果左表中没有匹配的行,则结果中左表的部分将包含 NULL

语法:

SELECT columns
FROM table1
RIGHT [OUTER] JOIN table2
ON table1.common_column = table2.common_column;

示例题目 3.3: 查询所有员工 (employees),并列出他们所属的部门名称(如果员工未分配部门,部门名称显示为 NULL)。

SELECT e.emp_name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.dept_id;
-- 注意:RIGHT JOIN 使用较少,通常用 LEFT JOIN 调整表顺序实现相同效果。例如上面的查询等价于:
SELECT e.emp_name, d.dept_name
FROM departments d
LEFT JOIN employees e ON d.dept_id = e.dept_id; -- 将 departments 作为左表

FULL JOIN (或 FULL OUTER JOIN):

  • 返回左表和右表中的所有行。当某行在另一个表中没有匹配时,另一个表的部分将包含 NULL
  • 注意: MySQL 不直接支持 FULL JOIN。但可以通过组合 LEFT JOINRIGHT JOIN 并使用 UNION 来模拟实现。

语法 (模拟):

SELECT columns FROM table1 LEFT JOIN table2 ON ...
UNION
SELECT columns FROM table1 RIGHT JOIN table2 ON ...;

示例题目 3.4: 查询所有部门和所有员工,列出所有可能的组合(有归属关系的显示关系,无归属关系的显示 NULL)。

SELECT d.dept_id, d.dept_name, e.emp_id, e.emp_name
FROM departments d
LEFT JOIN employees e ON d.dept_id = e.dept_id
UNION
SELECT d.dept_id, d.dept_name, e.emp_id, e.emp_name
FROM departments d
RIGHT JOIN employees e ON d.dept_id = e.dept_id
WHERE d.dept_id IS NULL; -- 排除 LEFT JOIN 部分已经包含的行
-- 更简洁的模拟(假设 dept_id 在 departments 和 employees 中都是非 NULL 且唯一):
SELECT d.dept_id, d.dept_name, e.emp_id, e.emp_name
FROM departments d
LEFT JOIN employees e ON d.dept_id = e.dept_id
UNION
SELECT d.dept_id, d.dept_name, e.emp_id, e.emp_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id
WHERE d.dept_id IS NULL;

CROSS JOIN:

  • 返回两个表的笛卡尔积,即左表的每一行与右表的每一行组合。不使用 ON 子句(或者使用无效的连接条件)。结果集的行数是左表行数乘以右表行数。

语法:

SELECT columns
FROM table1
CROSS JOIN table2;
-- 或者 (不推荐,易与 INNER JOIN 混淆)
SELECT columns
FROM table1, table2;

示例题目 3.5: 生成一个包含所有颜色 (colors) 和所有尺码 (sizes) 组合的列表。

SELECT color_name, size_name
FROM colors
CROSS JOIN sizes;

SELF JOIN:

  • 一个表与其自身进行连接。通常用于处理层次结构(如员工和经理)、比较同一表中的行。
  • 需要使用表别名来区分同一个表的两个实例。

语法:

SELECT a.column, b.column
FROM table_name a
JOIN table_name b ON a.common_column = b.common_column
WHERE condition; -- 通常需要额外条件来排除无效连接(如 a.id != b.id)

示例题目 3.6: 在 employees 表中,每个员工 (emp_id) 有一个经理 (manager_id),经理也是员工。查询每个员工及其经理的姓名。

SELECT e.emp_name AS employee_name, m.emp_name AS manager_name
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.emp_id; -- 使用 LEFT JOIN 处理没有经理的情况

示例题目 3.7: 在 products 表中,找出价格 (price) 相同的不同产品 (product_id)。

SELECT a.product_id AS product_a, b.product_id AS product_b, a.price
FROM products a
INNER JOIN products b ON a.price = b.price
WHERE a.product_id < b.product_id; -- 避免 (A, B) 和 (B, A) 重复,以及 (A, A)

多表连接:

可以连接两个以上的表。

语法:

SELECT ...
FROM table1
JOIN table2 ON condition1
JOIN table3 ON condition2
...
[WHERE ...]
[GROUP BY ...]
[HAVING ...];

示例题目 3.8: 有三个表:orders (order_id, customer_id, order_date), order_details (order_id, product_id, quantity), products (product_id, product_name, price)。查询订单号、客户ID、产品名称、购买数量和总金额 (quantity * price)。

SELECT o.order_id, o.customer_id, p.product_name, od.quantity, (od.quantity * p.price) AS total_amount
FROM orders o
JOIN order_details od ON o.order_id = od.order_id
JOIN products p ON od.product_id = p.product_id;

关键点总结 (JOIN):

  • JOIN 用于组合多个表中的数据。
  • 理解不同 JOIN 类型(INNER, LEFT, RIGHT, CROSS, SELF)的语义和行为差异。
  • 使用 ON 子句指定连接条件(通常是等值连接)。
  • 使用表别名简化多表查询,尤其是在 SELF JOIN 时。
  • 注意 NULL 值在 LEFT/RIGHT JOIN 中的处理。

第四部分:综合题目巩固

现在,我们将聚合函数、分组查询和联合查询结合起来,解决更复杂的问题。

题目 4.1: 在 ordersorder_detailsproducts 表结构下(假设 products 表有 category_idcategories 表有 category_id, category_name): 查询每个产品类别 (category_name) 在 2023 年的总销售额 (SUM(quantity * price)),并按销售额从高到低排序。

SELECT c.category_name, SUM(od.quantity * p.price) AS total_sales
FROM orders o
JOIN order_details od ON o.order_id = od.order_id
JOIN products p ON od.product_id = p.product_id
JOIN categories c ON p.category_id = c.category_id
WHERE YEAR(o.order_date) = 2023 -- 过滤年份
GROUP BY c.category_name -- 按类别分组
ORDER BY total_sales DESC; -- 按销售额降序排序

题目 4.2: 在 employees (emp_id, emp_name, salary, dept_id) 和 departments (dept_id, dept_name) 表下: 查询每个部门 (dept_name) 的平均工资 (AVG(salary)),并且只显示平均工资高于公司整体平均工资的部门。

SELECT d.dept_name, AVG(e.salary) AS avg_salary
FROM departments d
JOIN employees e ON d.dept_id = e.dept_id
GROUP BY d.dept_name
HAVING AVG(e.salary) > (SELECT AVG(salary) FROM employees); -- 子查询获取公司整体平均工资

题目 4.3: 在 students (student_id, student_name), courses (course_id, course_name), enrollments (student_id, course_id, grade) 表下: 查询每个学生 (student_name) 选修的课程数量 (COUNT(course_id)) 和平均成绩 (AVG(grade)),并按平均成绩降序排列。

SELECT s.student_name, COUNT(e.course_id) AS course_count, AVG(e.grade) AS avg_grade
FROM students s
LEFT JOIN enrollments e ON s.student_id = e.student_id -- 使用 LEFT JOIN 确保没选课的学生也被列出 (count=0, avg=null)
GROUP BY s.student_id, s.student_name -- 按学生分组 (通常按主键分组更安全)
ORDER BY avg_grade DESC; -- NULL 在降序排列中通常排在最后

题目 4.4: 在 orders (order_id, customer_id, order_date), customers (customer_id, customer_name, country), order_details (order_id, product_id, quantity), products (product_id, product_name) 表下: 查询 2023 年每个国家 (country) 的每个客户 (customer_name) 购买的不同产品数量 (COUNT(DISTINCT product_id)) 和总购买金额 (SUM(quantity * price)),仅列出总金额超过 1000 的客户。

SELECT c.country, c.customer_name,
       COUNT(DISTINCT od.product_id) AS distinct_products,
       SUM(od.quantity * p.price) AS total_spent
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_details od ON o.order_id = od.order_id
JOIN products p ON od.product_id = p.product_id
WHERE YEAR(o.order_date) = 2023
GROUP BY c.country, c.customer_id, c.customer_name -- 按国家和客户分组
HAVING total_spent > 1000
ORDER BY c.country, total_spent DESC;

题目 4.5: 在 employees (emp_id, emp_name, manager_id) 表下: 查询每个经理 (manager_name) 及其直接下属的数量 (COUNT(emp_id)) 和下属的平均工资 (假设有 salary 列),列出下属数量最多的前 5 位经理。

SELECT m.emp_name AS manager_name,
       COUNT(e.emp_id) AS direct_reports_count,
       AVG(e.salary) AS avg_subordinate_salary
FROM employees e
JOIN employees m ON e.manager_id = m.emp_id -- 连接自身,e 是下属,m 是经理
GROUP BY m.emp_id, m.emp_name -- 按经理分组
ORDER BY direct_reports_count DESC
LIMIT 5;

总结

  • 聚合函数 (COUNT, SUM, AVG, MAX, MIN, GROUP_CONCAT): 用于计算汇总统计值。
  • 分组查询 (GROUP BY): 用于将数据分成组,以便在每个组上应用聚合函数。HAVING 用于对分组结果进行筛选。
  • 联合查询 (JOIN): 用于组合多个相关表中的数据。掌握 INNER JOIN, LEFT JOIN, RIGHT JOIN, CROSS JOIN, SELF JOIN 的用法和区别是核心。
  • 综合运用: 在实际查询中,通常需要组合使用这些技术。理解 SQL 语句的执行顺序 (FROM -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT) 对于编写正确高效的查询至关重要。

到此这篇关于MySQL 聚合函数、分组、联合查询的文章就介绍到这了,更多相关mysql 聚合函数 分组 联合查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 查看MySQL初始密码并修改的正确方式

    查看MySQL初始密码并修改的正确方式

    这篇文章主要给大家介绍了关于查看MySQL初始密码并修改的正确方式,MySQL是一款广泛使用的开源关系型数据库管理系统,安装后找回初始密码是MySQL使用中的一个基础问题,需要的朋友可以参考下
    2023-10-10
  • mysql高可用之MHA用法及说明

    mysql高可用之MHA用法及说明

    MHA是MySQL高可用解决方案,实现0-30秒故障切换及数据一致性,Atlas是360读写分离中间件,提升查询效率,两者均用于数据库高可用架构优化,MHA侧重故障恢复,Atlas侧重负载均衡与读写分离
    2025-09-09
  • MySQL存储毫秒数据的方法

    MySQL存储毫秒数据的方法

    MySQL中没有可以直接存储毫秒数据的数据类型,但是不过MySQL却能识别时间中的毫秒部分。这篇文章主要介绍了MySQL存储毫秒数据的方法,需要的朋友可以参考下
    2014-06-06
  • mysql乱码问题分析与解决方法

    mysql乱码问题分析与解决方法

    开发过程中总避免不了遇到恶心的乱码,或者由乱码引发的一系列问题,这里简要介绍一下自己遇到的乱码问题和解决问题的过程中的想法以及大致的操作
    2012-11-11
  • MySQL8.0 DDL原子性特性及实现原理

    MySQL8.0 DDL原子性特性及实现原理

    这篇文章主要介绍了MySQL8.0 DDL原子性特性及实现原理,本文给大家介绍的非常详细,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2019-07-07
  • MySQL5.7.18修改密码的方法

    MySQL5.7.18修改密码的方法

    这篇文章主要介绍了MySQL5.7.18修改密码的方法,非常不错,具有参考解决价值,需要的朋友可以参考下
    2017-05-05
  • MySQL字符串截取指定字符串right使用示例

    MySQL字符串截取指定字符串right使用示例

    这篇文章主要为大家啊AI介绍了MySQL字符串截取指定字符串right使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • MYSQL命令行模式管理MySql的一点心得

    MYSQL命令行模式管理MySql的一点心得

    MYSQL命令行模式管理MySql的一点心得...
    2007-09-09
  • mysql中的一些稍微复杂用法实例代码

    mysql中的一些稍微复杂用法实例代码

    这篇文章主要给大家介绍了关于mysql中的一些稍微复杂用法的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用mysql具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-11-11
  • MySQL外键约束(Foreign Key)案例详解

    MySQL外键约束(Foreign Key)案例详解

    MySQL外键约束(FOREIGN KEY)是表的一个特殊字段,经常与主键约束一起使用,下面这篇文章主要给给大家介绍了关于MySQL外键约束(Foreign Key)的相关资料,需要的朋友可以参考下
    2022-06-06

最新评论