当前位置: 首页 > news >正文

mysql基础【多表查询经典案例】

📌 数据表结构说明

为便于理解,先明确涉及的三张表结构:

  • emp(员工表)
    字段:id, name, age, job, salary, dept_id, entrydate, managerid
    在这里插入图片描述

  • dept(部门表)
    字段:id, name
    在这里插入图片描述

  • salgrade(薪资等级表)
    字段:grade, losal(最低工资), hisal(最高工资)
    在这里插入图片描述


在这里插入图片描述

案例 1:查询所有已分配部门的员工基本信息及其所属部门名称

-- 使用隐式内连接(逗号连接)方式关联 emp 和 dept 表
-- 通过 WHERE 子句指定连接条件:员工的 dept_id 必须等于部门的 id
SELECTemp.name,        -- 员工姓名emp.age,         -- 员工年龄emp.job,         -- 员工职位dept.name        -- 所属部门名称
FROM emp, dept       -- 隐式连接两张表(笛卡尔积)
WHERE emp.dept_id = dept.id;  -- 过滤出有效关联记录(等价于 INNER JOIN)

在这里插入图片描述

⚠️ 说明

  • 这是 SQL-89 标准的写法,语法简单但可读性差;
  • 现代开发推荐使用显式 JOIN 以提高代码可维护性。

案例 2:查询年龄小于30岁且已分配部门的员工详细信息

-- 使用显式 INNER JOIN 明确表达连接逻辑,提高可读性和可维护性
-- 先连接员工与部门表,再通过 WHERE 过滤年龄条件
SELECTemp.name,        -- 员工姓名emp.age,         -- 员工年龄emp.job,         -- 员工职位dept.name        -- 所属部门名称
FROM emp
INNER JOIN dept ON emp.dept_id = dept.id  -- 显式内连接:仅保留有部门的员工
WHERE emp.age < 30;                       -- 业务条件:年龄小于30岁

在这里插入图片描述

💡 建议

  • 使用显式 JOIN 提高代码清晰度;
  • 将过滤条件放在 WHERE 中,避免混淆。

案例 3:查询至少有一名员工的部门信息(去重)

-- 使用 INNER JOIN 确保只返回存在员工关联的部门
-- 由于一个部门可能对应多名员工,直接查询会产生重复行
-- 因此使用 DISTINCT 消除重复,确保每个部门仅出现一次
SELECT DISTINCTdept.id,         -- 部门IDdept.name        -- 部门名称
FROM dept
INNER JOIN emp ON emp.dept_id = dept.id;  -- 仅保留有员工的部门

在这里插入图片描述

🔍 关键点

  • 使用 DISTINCTGROUP BY 来消除重复记录;
  • INNER JOIN 只会返回有匹配记录的行。

案例 4:查询年龄大于40岁的所有员工信息,包括未分配部门者(部门显示为 NULL)

-- 使用 LEFT JOIN 以 emp 为主表,确保即使 dept_id 为 NULL 也能保留员工记录
-- ⚠️ 注意:年龄过滤条件必须放在 WHERE 中,而非 ON 中
--     若放在 ON 中,会导致 ≤40 岁员工也被保留(仅部门为 NULL),不符合需求
SELECTemp.*,           -- 员工全部字段dept.name        -- 所属部门名称(未分配则为 NULL)
FROM emp
LEFT JOIN dept ON emp.dept_id = dept.id   -- 保留所有员工,尝试关联部门
WHERE emp.age > 40;                       -- 业务过滤:仅保留年龄 > 40 的员工

在这里插入图片描述

🚨 重点解析

  • LEFT JOIN 的核心是保留左表所有记录
  • 若将 emp.age > 40 放在 ON 中,只会过滤右表匹配逻辑,左表仍会返回所有员工(包括 ≤40 岁的);
  • 正确做法:先完成连接,再用 WHERE 筛选结果。

案例 5:为每位员工匹配对应的薪资等级(基于 salary 落在 [losal, hisal] 区间)

-- 使用 LEFT JOIN 确保即使某员工薪资不在任何等级范围内,也能显示其信息
-- 此时 salgrade 相关字段为 NULL,避免因无匹配而丢失员工数据
SELECTemp.*,               -- 员工全部信息salgrade.losal,      -- 该等级最低工资salgrade.hisal,      -- 该等级最高工资salgrade.grade       -- 对应薪资等级编号
FROM emp
LEFT JOIN salgradeON emp.salary BETWEEN salgrade.losal AND salgrade.hisal;  -- 区间匹配薪资等级

在这里插入图片描述

💡 技巧

  • 使用 BETWEEN 实现区间匹配;
  • LEFT JOIN 确保不丢失任何员工记录。

案例 6:查询“研发部”员工的完整信息及其对应的薪资等级

-- 需要同时关联三张表:emp(员工)、dept(部门)、salgrade(薪资等级)
-- 先 LEFT JOIN salgrade:保留所有员工的薪资等级(即使无匹配)
-- 再 INNER JOIN dept:因需限定“研发部”,未分配部门者可排除
-- 最后通过 WHERE 精确筛选部门名称
SELECTemp.*,               -- 员工全部字段salgrade.losal,      -- 薪资等级下限salgrade.hisal,      -- 薪资等级上限salgrade.grade       -- 薪资等级编号
FROM emp
LEFT JOIN salgrade ON emp.salary BETWEEN salgrade.losal AND salgrade.hisal
INNER JOIN dept ON emp.dept_id = dept.id
WHERE dept.name = '研发部';  -- 精确匹配部门名称

在这里插入图片描述

🔍 执行顺序

  1. 先关联 empsalgrade
  2. 再关联 dept 并筛选“研发部”。

案例 7:计算“研发部”员工的平均薪资

-- 通过 INNER JOIN 关联部门表,限定员工范围为“研发部”
-- 使用聚合函数 AVG() 计算平均值,并通过 AS 起别名提升可读性
SELECTAVG(emp.salary) AS '研发部平均薪资'  -- 聚合结果并命名
FROM emp
INNER JOIN dept ON emp.dept_id = dept.id
WHERE dept.name = '研发部';  -- 限定部门范围

在这里插入图片描述

💡 核心

  • 使用 AVG() 聚合函数计算平均值;
  • AS 给结果列起别名,提升可读性。

案例 8:查询薪资高于员工“灭绝”的所有员工信息

-- 使用标量子查询:子查询必须返回单个值(一行一列)
-- 假设“灭绝”是唯一存在的员工名,否则子查询将报错(返回多行)
-- 主查询将每位员工的 salary 与“灭绝”的 salary 进行比较
SELECTemp.*  -- 所有字段
FROM emp
WHERE emp.salary > (SELECT emp.salaryFROM empWHERE emp.name = '灭绝'  -- 获取“灭绝”的工资(标量值)
);

在这里插入图片描述

💡 标量子查询特点

  • 返回单个值;
  • 用于动态生成比较基准。

案例 9:查询薪资高于公司整体平均工资的员工

-- 子查询计算全公司员工的平均薪资(AVG(salary))
-- 主查询将每位员工 salary 与该平均值比较
-- 常用于“高于平均水平”的筛选场景(如高绩效员工识别)
SELECTemp.*  -- 员工全部信息
FROM emp
WHERE emp.salary > (SELECT AVG(emp.salary)  -- 标量子查询:公司平均工资FROM emp
);

在这里插入图片描述

💡 应用场景

  • 适用于需要与总体水平对比的场景;
  • 标量子查询简洁高效。

案例 10:查询薪资低于其所在部门平均工资的员工

法一:相关子查询(性能较低)

-- 使用相关子查询:子查询依赖外层查询的 dept_id
-- 对每位员工 e1,动态计算其所在部门的平均工资
-- 若 e1.salary 小于该平均值,则保留该记录
-- ⚠️ 性能注意:每行执行一次子查询,大数据量时效率较低
SELECTe1.*  -- 外层员工记录(别名 e1)
FROM emp AS e1
WHERE e1.salary < (SELECT AVG(e2.salary)        -- 计算同部门平均工资FROM emp AS e2WHERE e2.dept_id = e1.dept_id  -- 关联条件:与外层员工同部门
);
-- 等价伪代码逻辑:(实际上就是O(n*n)的for循环)
-- for 每个员工 e1 in emp:
--     avg_sal = SELECT AVG(salary) FROM emp WHERE dept_id = e1.dept_id
--     if e1.salary < avg_sal:
--         output e1

法二:分组聚合 + 内连接(推荐)

-- 等价高性能写法:先计算各部门平均工资,再与员工表关联筛选
-- 子查询按 dept_id 分组,一次性计算所有部门的平均薪资
-- 将结果作为临时表 dept_avg 与 emp 表连接
-- 最后筛选 salary < 部门平均工资 的员工
-- ✅ 优点:子查询仅执行一次,符合集合操作思想,适合大数据场景
SELECTemp.*
FROM emp
INNER JOIN (SELECTemp.dept_id,AVG(emp.salary) AS avg_salary  -- 每个部门的平均工资FROM empGROUP BY emp.dept_id               -- 按部门分组聚合
) AS dept_avg ON emp.dept_id = dept_avg.dept_id
WHERE emp.salary < dept_avg.avg_salary;  -- 筛选低于部门平均的员工

在这里插入图片描述

💡 优化建议

  • 尽量避免使用相关子查询,改用聚合 + JOIN 方案;
  • 提升查询性能,尤其在大数据集上表现更佳。

案例 11:查询所有部门信息,并统计每个部门的员工人数

法一:子查询方式

-- 使用子查询方式:对每个部门,单独统计其员工数量
SELECTdept.id,                     -- 部门IDdept.name,                   -- 部门名称(SELECT COUNT(*)             -- 子查询:统计员工表中属于当前部门的员工数量FROM emp                    -- 从员工表 emp 中查询WHERE emp.dept_id = dept.id -- 条件:员工的 dept_id 等于当前部门的 id) AS '人数'                  -- 将统计结果命名为“人数”
FROM dept;                       -- 从部门表 dept 中查询所有部门

法二:LEFT JOIN + GROUP BY 方式(推荐)

-- 使用 LEFT JOIN + GROUP BY 方式,确保即使没有员工的部门也会显示(人数为0)
SELECTdept.id,                     -- 部门IDdept.name,                   -- 部门名称COUNT(emp.id) AS '人数'      -- 统计每个部门关联的员工数量(使用 COUNT(emp.id) 而非 COUNT(*))
FROMdept
LEFT JOIN emp ON dept.id = emp.dept_id  -- 左连接:保留所有部门,即使没有员工
GROUP BYdept.id, dept.name;          -- 按部门分组,确保每个部门一行

在这里插入图片描述

💡 最佳实践

  • 使用 LEFT JOIN 保留所有部门,即使无员工;
  • COUNT(emp.id) 统计非空记录数,避免误计入 NULL 值。

📌 数据表结构说明

  • student(学生表)
    字段: id, name,no
    在这里插入图片描述

  • course(课程表)
    字段: id, name
    在这里插入图片描述

  • student_course(选课中间表,实现多对多关系)
    字段: studentid, courseid
    在这里插入图片描述


在这里插入图片描述

案例 12:查询所有学生的选课情况,展示学生姓名、学号及所选课程名称

-- 12.查询所有学生的选课情况,展示出学生名称,学号,课程名称
-- 外连接
-- 展示字段:学生姓名、学号、所选课程名称(未选课则课程名称为 NULL)
SELECTstudent.name,        -- 学生姓名student.no,          -- 学生学号course.name          -- 所选课程名称(若学生未选课,则此字段为 NULL)
FROM student
-- 第一步:将学生表与选课中间表关联,通过学生ID匹配选课记录
LEFT JOIN student_course ON student.id = student_course.studentid
-- 第二步:将选课记录与课程表关联,通过课程ID获取课程名称
LEFT JOIN course ON student_course.courseid = course.id;

在这里插入图片描述

关键设计

  • 使用 两次 LEFT JOIN
    1. studentstudent_course:确保即使学生没有选课记录,也能保留其基本信息;
    2. student_coursecourse:通过选课记录中的 courseid 获取课程名称;
  • 若某学生无任何选课记录,则 student_coursecourse 表的字段均为 NULL,但学生信息仍会显示。

http://www.dtcms.com/a/492030.html

相关文章:

  • 智慧医疗新基建:医院三维可视化运维与IBMS集成探秘
  • 南昌网站网页设计广州手机网站定制信息
  • 网站建设工作会议.php语言网站开发
  • AI工程化:MLflow模型管理,生命周期怎样跟踪?
  • 抽象类需要用spring其他service,怎么写
  • SpringBoot-配置文件yaml
  • 一个网站设计的费用全国小学网站建设
  • so域名网站wordpress自动网站地址
  • Parasoft自动化测试工具与解决方案:实现规模化应用
  • 萧山网站建设微信 话潍坊站总站人工服务电话
  • 网站开发合同受托方wordpress中文主题站
  • STM32F103C8T6称重传感器HX711模块压力传感器称重的使用方法和代码驱动
  • 迎访问备案网站管理系统成都房产网官网
  • 行业网站建设内容怎么做网站寄生虫
  • 我的世界做外国壁纸网站竞价培训班
  • 重庆网站推广大全wordpress 早起文章
  • 高压断路器故障诊断:基于注意力机制的少样本迁移学习(FSTL)方法详解
  • 湖南专业外贸建站公司手机版网站如何做
  • 建设网站有哪些公司wordpress的开发框架
  • Linux Shell 实验一:数据采集与脚本调试错误总结
  • RHCSA作业1
  • 邹城有做网站的吗网站建设坂田
  • 外贸自助建站上海市企业网站建设
  • python(入门)函数及lambda表达式
  • 衡水市网站建设seo推广软件下载
  • 提供邯郸移动网站建设高职专业建设管理网站
  • ros2常用命令
  • 网站建设维护要求济南做网站哪里好
  • 2个网站 同意内容 百度优化什么网站专门做二手物品
  • 【AI大模型学习路线】第三阶段之RAG与LangChain——第十九章(实战基于Advanced RAG的PDF问答)系统部署与测试?