MySQL数据库表关系与多表查询:从表间关系设计到JOIN联表查询

第一部分:数据库表间关系

1.1 三种关系概述

老师强调:表间关系只有三种------一对一、一对多、多对多。不存在独立的"多对一",它只是一对多的反向视角,不要单列。

关系类型 举例 最佳处理方案
一对一 丈夫 ↔ 妻子 合并成一张表
一对多 班级 → 学生 在"多"的一方加外键
多对多 学生 ↔ 课程 创建中间表

1.2 一对一关系

老师强调 :一对一关系最佳处理方式是合并成一张表。把两边信息放同一行,如丈夫名和妻子名并排,同处一行即表示对应关系。查询效率最高,因为只需查单表。

丈夫信息 妻子信息
张三 李华
李四 张明
王五 丽丽

老师强调 :合并成一张表后,夫妻关系被直接绑定,不能填别人。用两张表加外键虽能存关系,但本质也是一行绑定一个,没有更灵活。一张表查询效率远高于多表联查,因为计算机按页存储,相关信息放同一页,分表后底层识别不到关联字段,会放不同页。

不推荐方案(了解即可):

  • 在妻子表加外键指向丈夫表
  • 建第三张关系表存丈夫id和妻子id(三表联查效率最低)

1.3 一对多关系

老师强调 :一对多关系的标准示例是班级和学生------一个班级对应多个学生,但一个学生只属于一个班级。站在学生角度看是多对一,站在班级看是一对多,本质同一关系。

错误方案:合并成一张表

学生名 班级名 班级人数
张三 软件工程班 50
李四 软件工程班 50
王五 软件工程班 50

老师强调 :合并表造成数据冗余 ------班级信息随学生数量重复存储,有100个学生就存100遍。设计原则不允许数据重复,所以此方案不可行。

✅ 正确方案:在"多"的一方加外键

复制代码
班级表(一)
┌────┬──────────────┐
│ id │ classname    │
├────┼──────────────┤
│ 1  │ 软件工程班    │
│ 2  │ 大数据班      │
└────┴──────────────┘

学生表(多)------加外键cid指向班级表id
┌────┬──────┬─────┐
│ id │ name │ cid │
├────┼──────┼─────┤
│ 1  │ 张三 │ 1   │
│ 2  │ 李四 │ 1   │
│ 3  │ 王五 │ 2   │
└────┴──────┴─────┘

老师强调:外键是多了个引用值,值相同不算冗余。外键字段加在学生表(多的一方),而非班级表(一的一方)。若在班级表加学生id,单元格只能存一条信息,软件工程班存张三后李四得另起行,班级信息重复存储。

结论:一对多关系 → 在"多"的一方加外键,不要建中间表(三表联查更慢)。

1.4 多对多关系

老师强调 :多对多关系的典型示例是学生和课程------一个学生可选多门课,一门课也可被多个学生选。从单方看都是一对多,但双向都可一对多即多对多。

错误方案:合并成一张表

学生名 课程名 学分
张三 数据结构 3
张三 C语言 4
李四 数据结构 3

老师强调 :合并表导致双方数据都冗余 ------张三选10门课得存10遍张三信息,数据结构被2人选得存2遍课程信息。双方信息都重复,不可接受。

错误方案:加外键

  • 学生表加课程id字段→只能存单个值,不能表示"1,2,3"
  • 课程表加学生id字段→同样问题,违反存储规范

✅ 正确方案:创建中间表

复制代码
学生表(存学生信息)         课程表(存课程信息)
┌────┬──────┐              ┌────┬──────────┬────┐
│ id │ name │              │ id │ cname    │credit│
├────┼──────┤              ├────┼──────────┼────┤
│ 1  │ 张三 │              │ 1  │ 数据结构 │ 3  │
│ 2  │ 李四 │              │ 2  │ C语言   │ 4  │
└────┴──────┘              └────┴──────────┴────┘

中间表(选课关系 + 成绩)
┌────┬─────┬─────┬──────┐
│ id │ sid │ cid │ score│
├────┼─────┼─────┼──────┤
│ 1  │ 1   │ 1   │ 85   │
│ 2  │ 1   │ 2   │ 92   │
│ 3  │ 2   │ 1   │ 78   │
└────┴─────┴─────┴──────┘

老师强调:中间表像"桥",把学生和课程解耦。中间表的sid存学生id,cid存课程id,成绩也放中间表。双方核心信息只存一遍,无冗余。宁可接受多表联查的性能损耗,也不容忍数据重复。

1.5 三种关系方案总结

关系 最佳方案 原因
一对一 合并成一张表 单表查询效率最高
一对多 在"多"方加外键 避免一方的数据重复
多对多 创建中间表 避免双方数据重复

第二部分:多表查询与联表查询

2.1 数据准备

复制代码
-- 学生表
CREATE TABLE student (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20),
    cid INT  -- 指向班级id
);

-- 班级表
CREATE TABLE class (
    id INT PRIMARY KEY AUTO_INCREMENT,
    classname VARCHAR(50)
);

-- 示例数据
INSERT INTO student VALUES (1, '张三', 1), (2, '李四', 1), (3, '王五', 2), (4, '赵六', NULL);
INSERT INTO class VALUES (1, '软件工程班'), (2, '大数据班'), (3, '人工智能班');

2.2 多表查询(隐式内连接)

老师强调 :多表查询用逗号分隔表名,在WHERE中写连接条件。不加条件会产生笛卡尔积------A表4条×B表10条=40条,每条学生匹配每个班级。

复制代码
-- ❌ 错误:不加条件 → 笛卡尔积(14行)
SELECT * FROM student, class;  -- 7条×2条=14条

-- ✅ 正确:加连接条件
SELECT student.*, class.classname 
FROM student, class 
WHERE student.cid = class.id;

老师强调 :多表查询本质是内连接,只保留两表都能匹配的记录。空值不参与匹配,所以未分班的赵六、无学生的班级在结果中"消失"------容易误以为数据完整,实则丢了记录。

2.3 联表查询(JOIN)

三种JOIN对比:

复制代码
-- 内连接:只保留匹配记录(等价于多表查询)
SELECT * FROM student JOIN class ON student.cid = class.id;

-- 左外连接:保留左表全部 + 右表匹配部分
SELECT * FROM student LEFT JOIN class ON student.cid = class.id;

-- 右外连接:保留右表全部 + 左表匹配部分
SELECT * FROM student RIGHT JOIN class ON student.cid = class.id;
连接类型 保留内容 使用场景
INNER JOIN 两表都匹配的记录 只想看有对应关系的数据
LEFT JOIN 左表全部 + 右表匹配 以左表为主,保留左表所有数据
RIGHT JOIN 右表全部 + 左表匹配 以右表为主,保留右表所有数据

示例效果:

复制代码
student LEFT JOIN class:
┌────┬──────┬─────┬─────────────┐
│ id │ name │ cid │ classname   │
├────┼──────┼─────┼─────────────┤
│ 1  │ 张三 │ 1   │ 软件工程班   │
│ 2  │ 李四 │ 1   │ 软件工程班   │
│ 3  │ 王五 │ 2   │ 大数据班    │
│ 4  │ 赵六 │NULL │ NULL        │ ← 左表无匹配也保留
└────┴──────┴─────┴─────────────┘

student RIGHT JOIN class:
┌────┬──────┬─────┬─────────────┐
│ id │ name │ cid │ classname   │
├────┼──────┼─────┼─────────────┤
│ 1  │ 张三 │ 1   │ 软件工程班   │
│ 2  │ 李四 │ 1   │ 软件工程班   │
│ 3  │ 王五 │ 2   │ 大数据班    │
│NULL│ NULL │NULL │ 人工智能班   │ ← 右表无匹配也保留
└────┴──────┴─────┴─────────────┘

2.4 表重命名(别名)

老师强调:重命名后用新名,原表名失效。字段名冲突时必须加表名/别名前缀区分。

复制代码
-- 表重命名
SELECT * FROM student s JOIN class c ON s.cid = c.id;

-- 字段重命名(AS可省略)
SELECT s.name AS 学生姓名, c.classname AS 班级名称 
FROM student s JOIN class c ON s.cid = c.id;

2.5 多表联查示例(三表)

复制代码
-- 查询学生选了哪些课、成绩多少
-- 学生表 → 选课表 → 课程表
SELECT s.name, c.cname, sc.score
FROM student s 
JOIN sc ON s.id = sc.sid 
JOIN course c ON sc.cid = c.id;

老师强调:多表JOIN时,每个JOIN都需要ON条件。学生表和课程表无直接关联,需通过选课表中转:先JOIN选课表,再JOIN课程表。

第三部分:嵌套查询

3.1 什么是嵌套查询

老师强调 :嵌套查询是一个SQL查询的结果作为另一个SQL的条件或临时表使用。即查询嵌套进查询里

子查询的两种用法:

用法 位置 说明
作为条件 WHERE子句中 子查询结果作为过滤条件
作为临时表 FROM子句中 子查询结果当成表参与JOIN

3.2 子查询作为条件

复制代码
-- 需求:查出至少有一科考过85分的学生信息
-- 步骤1:查成绩表,找出考过85分的学生id
SELECT sid FROM sc WHERE score > 85;  -- 得到 1, 2

-- 步骤2:用这些id去学生表查信息
SELECT * FROM student WHERE id IN (1, 2);

-- 嵌套写法(一条SQL完成)
SELECT * FROM student 
WHERE id IN (SELECT sid FROM sc WHERE score > 85);

3.3 子查询作为临时表

老师强调 :子查询结果可以当成一张临时表,在FROM子句中重命名后参与JOIN。

复制代码
-- 把查询结果当临时表使用
SELECT s1.*, temp.cname 
FROM course s1 
JOIN (SELECT cid, cname FROM course WHERE credit > 3) temp 
ON s1.id = temp.cid;

附录一:完整SQL示例

复制代码
-- 1. 多表查询(隐式内连接)
SELECT student.*, class.classname 
FROM student, class 
WHERE student.cid = class.id;

-- 2. 内连接(等价)
SELECT * FROM student JOIN class ON student.cid = class.id;

-- 3. 左外连接
SELECT * FROM student LEFT JOIN class ON student.cid = class.id;

-- 4. 右外连接
SELECT * FROM student RIGHT JOIN class ON student.cid = class.id;

-- 5. 表重命名
SELECT * FROM student s JOIN class c ON s.cid = c.id;

-- 6. 三表联查
SELECT s.name, c.cname, sc.score
FROM student s 
JOIN sc ON s.id = sc.sid 
JOIN course c ON sc.cid = c.id;

-- 7. 嵌套查询(作为条件)
SELECT * FROM student 
WHERE id IN (SELECT sid FROM sc WHERE score > 85);

-- 8. 嵌套查询(作为临时表)
SELECT s1.*, temp.cname 
FROM course s1 
JOIN (SELECT cid, cname FROM course WHERE credit > 3) temp 
ON s1.id = temp.cid;

附录二:核心易错点速查表

类别 易错点 正确理解
一对一 分两张表加外键 最佳方案是合并成一张表
一对多 在一的一方加外键 多的一方加外键
多对多 在某一张表加外键 必须创建中间表
外键 vs 冗余 以为加外键就是冗余 加外键只是引用值,不算冗余
多表查询不加条件 以为直接出正确结果 产生笛卡尔积
INNER JOIN 以为能保留所有数据 只保留匹配记录,无匹配的会消失
LEFT/RIGHT JOIN 左右表搞反 看表书写顺序,左表全部保留
表重命名 以为旧名还能用 重命名后必须用新名
JOIN条件 多个JOIN只写一个ON 每个JOIN都要有ON条件
嵌套查询 混淆"当条件"和"当表" 条件在WHERE,临时表在FROM

老师总结 :表间关系三种------一对一合并表,一对多"多"方加外键,多对多建中间表。多表查询用逗号+WHERE本质是内连接,只保留匹配记录;联表查询用JOIN,INNER JOIN等价多表查询,LEFT/RIGHT JOIN可保留一侧全部数据。嵌套查询是子查询结果作为条件(WHERE)或临时表(FROM)使用。外键在开发中一般不在数据库设约束,靠业务代码控制取值合法性。 多表查询关键是连接条件,写对才能出正确结果。

相关推荐
网络工程小王1 小时前
【LLM开发实验】FastAPI 学习笔记
数据库·笔记·学习·mysql·fastapi
Aime_Perfect1 小时前
sqlserver always on
服务器·数据库·sqlserver
摘星编程1 小时前
从“数据出库“到“模型入库“:DolphinDB 库内机器学习全流程实践
数据库
Sagittarius_A*2 小时前
【好靶场】SQL注入-时间盲注
数据库·sql
神明不懂浪漫2 小时前
【第二章】库、表、增删改查操作
开发语言·数据库·经验分享·笔记
其实防守也摸鱼2 小时前
OpenClaw 深度解析:从原理到实战的完整指南
运维·服务器·数据库·github·copilot
hzp6662 小时前
doris学习6:参数调优
数据库·doris·参数·数据库调优
盟道科技3 小时前
小程序电商订单超时自动取消的三种实现方案:定时扫描、Redis 过期监听、延迟消息对比与生产落地
数据库·redis·小程序
剑锋所指,所向披靡!3 小时前
MySQL存储引擎
数据库·mysql