【Tilas|第三篇】多表SQL语句

目录

前言

一、引言

多表关系:

1.一对多关系

[1.1 概念](#1.1 概念)

[1.2 设计原则](#1.2 设计原则)

[1.3 实例:部门与员工](#1.3 实例:部门与员工)

[1.4 添加物理外键约束](#1.4 添加物理外键约束)

2.一对一关系

[2.1 概念](#2.1 概念)

[2.2 设计原则](#2.2 设计原则)

[2.3 实例:用户与身份证信息](#2.3 实例:用户与身份证信息)

3.多对多关系

[3.1 概念](#3.1 概念)

[3.2 设计原则](#3.2 设计原则)

[3.3 实例:学生与课程](#3.3 实例:学生与课程)

4.物理外键与逻辑外键的区别

[4.1 物理外键](#4.1 物理外键)

[4.2 逻辑外键](#4.2 逻辑外键)

5.总结

[5.1 表关系设计要点](#5.1 表关系设计要点)

[5.2 外键选择建议](#5.2 外键选择建议)

多表查询

[1.内连接(INNER JOIN)](#1.内连接(INNER JOIN))

[1.1 概念](#1.1 概念)

[1.2 隐式内连接](#1.2 隐式内连接)

[1.3 显式内连接](#1.3 显式内连接)

[1.4 带条件的内连接](#1.4 带条件的内连接)

[2. 外连接(OUTER JOIN)](#2. 外连接(OUTER JOIN))

[2.1 概念](#2.1 概念)

[2.2 左外连接(LEFT JOIN)](#2.2 左外连接(LEFT JOIN))

[2.3 右外连接(RIGHT JOIN)](#2.3 右外连接(RIGHT JOIN))

[2.4 外连接的实际应用](#2.4 外连接的实际应用)

[3. 子查询(Subquery)](#3. 子查询(Subquery))

[3.1 概念](#3.1 概念)

[3.2 标量子查询](#3.2 标量子查询)

[3.3 列子查询](#3.3 列子查询)

[3.4 行子查询](#3.4 行子查询)

[3.5 表子查询](#3.5 表子查询)

[4. 三种连接方式的区别](#4. 三种连接方式的区别)

[5. 实战需求练习](#5. 实战需求练习)

[5.1 需求1:查询"教研部"性别为男,且在"2011-05-01"之后入职的员工信息](#5.1 需求1:查询"教研部"性别为男,且在"2011-05-01"之后入职的员工信息)

[5.2 需求2:查询工资低于公司平均工资的且性别为男的员工信息](#5.2 需求2:查询工资低于公司平均工资的且性别为男的员工信息)

[5.3 需求3:查询部门人数超过10人的部门名称](#5.3 需求3:查询部门人数超过10人的部门名称)

[5.4 需求4:查询在"2010-05-01"后入职,且薪资高于10000的"教研部"员工信息,按薪资倒序排序](#5.4 需求4:查询在"2010-05-01"后入职,且薪资高于10000的"教研部"员工信息,按薪资倒序排序)

[5.5 需求5:查询工资低于本部门平均工资的员工信息](#5.5 需求5:查询工资低于本部门平均工资的员工信息)

注意:交叉连接(笛卡尔积)

[6. 总结](#6. 总结)

过滤条件补充说明:

[1. 过滤条件的区别(WHERE、ON、HAVING)](#1. 过滤条件的区别(WHERE、ON、HAVING))

[1.1WHERE 子句](#1.1WHERE 子句)

[1.2 ON 子句](#1.2 ON 子句)

[1.3 HAVING 子句](#1.3 HAVING 子句)

[1.4 三者对比](#1.4 三者对比)

[1.5 ON 和 WHERE 在外连接中的区别](#1.5 ON 和 WHERE 在外连接中的区别)

[1.6 SQL 的常见执行顺序](#1.6 SQL 的常见执行顺序)

[2 GROUP BY 的使用](#2 GROUP BY 的使用)

[2.1 基本用法](#2.1 基本用法)

[2.2 GROUP BY 的规则](#2.2 GROUP BY 的规则)

[2.3 GROUP BY 可以按照多个字段分组。](#2.3 GROUP BY 可以按照多个字段分组。)

[2.4 GROUP BY 与聚合函数](#2.4 GROUP BY 与聚合函数)

总结


前言

今天我们来学习MySQL关于多表关系和查询的具体实现,这些SQL语句会最终写在xml文件或者Mapper接口上,有着至关重要性。


引言

在数据库设计中,表与表之间的关系是核心概念。合理的表关系设计不仅能保证数据的完整性和一致性,还能提高查询效率。


多表关系:

1.一对多关系

1.1 概念

一对多关系是数据库中最常见的关系类型。一个表中的一条记录可以对应另一个表中的多条记录,但反过来不成立。

1.2 设计原则

在"多"的一方添加外键字段,关联"一"方的主键。

1.3 实例:部门与员工

复制代码
-- 部门表(一方)

create table dept(

    id int unsigned primary key auto_increment comment 'ID, 主键',

    name varchar(10) not null unique comment '部门名称',

    create_time datetime comment '创建时间',

    update_time datetime comment '修改时间'

) comment '部门表';

-- 员工表(多方)

create table emp(

    id int unsigned primary key auto_increment comment 'ID,主键',

    username varchar(20) not null unique comment '用户名',

    password varchar(32) not null comment '密码',

    name varchar(10) not null comment '姓名',

    gender tinyint unsigned not null comment '性别, 1:男, 2:女',

    phone char(11) not null unique comment '手机号',

    job tinyint unsigned comment '职位',

    salary int unsigned comment '薪资',

    entry_date date comment '入职日期',

    dept_id int unsigned COMMENT '关联的部门ID',

    create_time datetime comment '创建时间',

    update_time datetime comment '修改时间'

) comment '员工表';

1.4 添加物理外键约束

复制代码
alter table子表 add constraint   fk__表名__引用字段名 foreign key (字段名)references 父表(父表的关联);

alter table emp add constraint  fk_emp_dept_id foreign key (dept_id)references dept(id);

也可以基于图形化页面操作

右键-修改表-外键- 目标表:父表名,列名:1,目标名称:父表的主键

说明

  • 一个部门可以有多个员工
  • 一个员工只能属于一个部门
  • 在员工表的dept_id字段上建立外键,引用部门表的主键id

2.一对一关系

2.1 概念

一对一关系表示两个表之间的记录是一一对应的。这种关系相对少见,通常用于:

  • 拆分大表,提高查询效率
  • 存储可选的扩展信息

2.2 设计原则

在任意一方添加外键字段,并设置为unique,确保一对一的约束。

2.3 实例:用户与身份证信息

复制代码
-- 用户信息表

create table tb_user(

    id int unsigned primary key auto_increment comment 'ID',

    name varchar(10) not null comment '姓名',

    gender tinyint unsigned not null comment '性别, 1 男 2 女',

    phone char(11) comment '手机号',

    degree varchar(10) comment '学历'

) comment '用户信息表';

-- 用户身份证信息表

create table tb_user_card(

    id int unsigned primary key auto_increment comment 'ID',

    nationality varchar(10) not null comment '民族',

    birthday date not null comment '生日',

    idcard char(18) not null comment '身份证号',

    issued varchar(20) not null comment '签发机关',

    expire_begin date not null comment '有效期限-开始',

    expire_end date comment '有效期限-结束',

    user_id int unsigned not null unique comment '用户ID',

    constraint fk_user_id foreign key (user_id) references tb_user(id)

) comment '用户身份证表';

说明

  • user_id字段设置了unique约束,确保一个身份证信息只能对应一个用户
  • 外键约束保证了数据的完整性

3.多对多关系

3.1 概念

多对多关系表示两个表之间的记录可以相互对应多条。例如学生与课程的关系:一个学生可以选修多门课程,一门课程可以被多个学生选修。

3.2 设计原则

需要创建一个中间表(也叫关联表),包含两个外键分别指向两个表的主键。

3.3 实例:学生与课程

复制代码
-- 学生表

create table tb_student(

    id int auto_increment primary key comment '主键ID',

    name varchar(10) comment '姓名',

    no varchar(10) comment '学号'

) comment '学生表';

-- 课程表

create table tb_course(

    id int auto_increment primary key comment '主键ID',

    name varchar(10) comment '课程名称'

) comment '课程表';

-- 学生课程中间表

create table tb_student_course(

    id int auto_increment comment '主键' primary key,

    student_id int not null comment '学生ID',

    course_id int not null comment '课程ID',

    constraint fk_courseid foreign key (course_id) references tb_course(id),

    constraint fk_studentid foreign key (student_id) references tb_student(id)

) comment '学生课程中间表';

说明

  • 中间表存储了学生与课程的关联关系
  • 每个关联记录表示一个学生选修了一门课程
  • 通过两个外键分别约束学生表和课程表

4.物理外键与逻辑外键的区别

4.1 物理外键

定义:通过FOREIGN KEY约束在数据库层面强制建立的外键关系。

特点

-- 约束强制关联:在数据库表结构中显式定义外键关系,并通过 FOREIGN KEY 约束强制确保数据完整性

复制代码
-- 物理外键示例

alter table emp add constraint fk_emp_dept_id 

foreign key (dept_id) references dept(id);

优势

  • 数据完整性:数据库自动保证引用完整性
  • 级联操作:支持级联删除、级联更新
  • 自动校验:插入或更新时自动检查引用是否存在

劣势

  • 性能开销:外键约束会降低写入性能
  • 灵活性差:修改外键关系需要DDL操作
  • 分布式困难:在分布式系统中难以维护

4.2 逻辑外键

定义:不在数据库层面建立外键约束,而是通过业务逻辑来维护表之间的关联关系。

特点

-- 逻辑外键:仅定义字段,不添加FOREIGN KEY约束

复制代码
create table emp(

    id int unsigned primary key auto_increment,

    dept_id int unsigned comment '关联的部门ID',  -- 逻辑外键

    ...

);

优势

  • 性能优秀:避免外键约束的性能开销
  • 灵活性高:修改关联关系无需DDL操作
  • 分布式友好:易于在分布式系统中实现
  • 便于测试:测试时无需维护复杂的外键关系

劣势

  • 数据风险:需要业务代码保证数据完整性
  • 无自动校验:数据库不会自动检查引用有效性
  • 开发负担:增加了业务层的复杂度

5.总结

5.1 表关系设计要点

  1. 一对多:在多方添加外键,关联一方主键
  2. 一对一:在任意一方添加外键,并设置unique约束
  3. 多对多:创建中间表,包含两个外键分别指向两个表

5.2 外键选择建议

  • 物理外键:适合数据一致性要求高、写入频率相对较低的场景
  • 逻辑外键:适合高并发、分布式、需要灵活扩展的场景

多表查询

1.内连接(INNER JOIN)

1.1 概念

内连接是最常用的连接方式,它只返回两个表中满足连接条件的记录,相当于两个集合的 交集 。

1.2 隐式内连接

通过 WHERE 子句指定连接条件:

复制代码
-- 查询所有员工的ID、姓名及所属部门名称
select emp.id, emp.name, dept.name 
from emp, dept 
where dept_id = dept.id;

1.3 显式内连接

使用 JOIN 关键字明确指定连接类型:

复制代码
-- 显式内连接语法
select emp.id, emp.name 
from emp inner join dept on dept_id = dept.id;

-- INNER 关键字可省略
select emp.id, emp.name 
from emp join dept on dept_id = dept.id;

1.4 带条件的内连接

复制代码
-- 查询男性且工资大于8000的员工信息
select emp.id, emp.name, dept.name 
from emp, dept 
where dept_id = dept.id and salary > 8000 and gender = 1;

-- 使用表别名
select e.id, e.name, d.name 
from emp e, dept d 
where e.dept_id = d.id and e.salary > 8000 and e.gender = 1;

2. 外连接(OUTER JOIN)

2.1 概念

外连接返回满足条件的所有记录,即使一方没有匹配的记录也会返回(用 NULL 填充)。

2.2 左外连接(LEFT JOIN)

返回左表的所有记录,以及右表中满足条件的记录。如果右表没有匹配,则右表字段为 NULL。

复制代码
 -- 查询所有员工及其部门(包括没有部门的员工)
  select e.name as 员工姓名, d.name as 部门名称
  from emp e
  left outer join dept d on e.dept_id = d.id;

2.3 右外连接(RIGHT JOIN)

返回右表的所有记录,以及左表中满足条件的记录。如果左表没有匹配,则左表字段为 NULL。

复制代码
  -- 查询所有部门及其员工(包括没有员工的部门)
  select d.name as 部门名称, e.name as 员工姓名
  from emp e
  right outer join dept d on e.dept_id = d.id;

2.4 外连接的实际应用

复制代码
  -- 查询工资高于8000的所有员工姓名及其部门名称
  select *
  from emp e
  left outer join dept d on e.dept_id = d.id
  where salary > 8000;

3. 子查询(Subquery)

3.1 概念

子查询是嵌套在其他 SQL 语句中的查询,也称为嵌套查询。

3.2 标量子查询

返回单个值(一行一列)的子查询。

复制代码
  -- 查询最早入职的员工信息
  select name
  from emp
  where entry_date = (select min(entry_date) from emp);

  -- 查询在"阮小五"入职之后入职的员工信息
  select emp.name
  from emp
  where entry_date > (select emp.entry_date from emp where name = '阮小五');

3.3 列子查询

返回一列多行的子查询,通常与 IN 操作符配合使用。

复制代码
  -- 查询"教研部"和"咨询部"的所有员工信息
  select *
  from emp
  where dept_id in (select id from dept where name = '教研部' or name = '咨询部');

3.4 行子查询

返回一行多列的子查询,用于比较多个字段。

复制代码
  -- 查询与"李忠"薪资及职位都相同的员工信息
  select *
  from emp
  where (salary, job) = (select salary, job from emp where name = '李忠');

3.5 表子查询

返回多行多列的子查询,作为临时表使用。

复制代码
  -- 查询与"李忠"薪资及职位都相同的员工信息
  select *
  from emp
  where (salary, job) = (select salary, job from emp where name = '李忠');

4. 三种连接方式的区别

  • 内连接:取两个表的交集,只返回匹配的记录
  • 左外连接:保留左表全部记录,右表无匹配时用 NULL 填充
  • 右外连接:保留右表全部记录,左表无匹配时用 NULL 填充

5. 实战需求练习

5.1 需求1:查询"教研部"性别为男,且在"2011-05-01"之后入职的员工信息

复制代码
  select *
  from emp e, dept d
  where e.dept_id = d.id
  and gender = 1
  and entry_date > '2011-05-01'
  and d.name = '教研部';

5.2 需求2:查询工资低于公司平均工资的且性别为男的员工信息

复制代码
  select *
  from emp
  where salary < (select avg(salary) from emp)
  and gender = 1;

5.3 需求3:查询部门人数超过10人的部门名称

复制代码
  select *
  from emp
  where salary < (select avg(salary) from emp)
  and gender = 1;

5.4 需求4:查询在"2010-05-01"后入职,且薪资高于10000的"教研部"员工信息,按薪资倒序排序

复制代码
select *
  from emp e, dept d
  where d.id = e.dept_id
  and e.entry_date > '2010-05-01'
  and salary > 10000
  and d.name = '教研部'
  order by e.salary desc;

5.5 需求5:查询工资低于本部门平均工资的员工信息

复制代码
  select e.*
  from emp e, (select dept_id, avg(salary) avg_sal from emp group by dept_id) d
  where e.dept_id = d.dept_id
  and e.salary < d.avg_sal;

注意:交叉连接(笛卡尔积)

-- 不推荐:返回两个表的笛卡尔积

select * from emp, dept;

-- 正确:应始终使用连接条件

select * from emp join dept on emp.dept_id = dept.id;

6. 总结

查询类型 关键字 特点
内连接 JOIN / INNER JOIN 返回交集
左外连接 LEFT JOIN / LEFT OUTER JOIN 保留左表全部
右外连接 RIGHT JOIN / RIGHT OUTER JOIN 保留右表全部
子查询 () 嵌套分步计算,逻辑清晰

过滤条件补充说明:

1. 过滤条件的区别(WHERE、ON、HAVING)

在 SQL 查询中,`WHERE`、`ON` 和 `HAVING` 都可以用于设置条件,但它们的作用对象和执行阶段并不相同。

如果混用不当,尤其是在多表连接和分组统计时,很容易得到错误结果。

1.1WHERE 子句

`WHERE` 用于过滤原始数据行,通常在分组之前生效。

适用场景:

  • 单表条件筛选
  • 多表查询后的行级过滤
  • 不涉及聚合结果的条件判断

特点:

  • 过滤的是"行"

  • 一般不能直接使用聚合函数

  • 常用于 `SELECT`、`UPDATE`、`DELETE` 等语句中

    -- 查询工资大于 8000 的男性员工
    select *
    from emp
    where salary > 8000 and gender = 1;

1.2 ON 子句

ON 用于指定表与表之间的连接条件,决定两张表如何匹配。

适用场景:

  • JOIN、LEFT JOIN、RIGHT JOIN 等连接查询
  • 指定两表之间的关联关系
  • 在外连接中控制"匹配条件"

特点:

  • 过滤的是"连接关系"

  • 主要用于 JOIN 后面

  • 在外连接中,ON 和 WHERE 的写法不同,结果也可能不同

    复制代码
    -- 左外连接:只关联部门名称为"教研部"的部门信息
    select e.name, d.name
    from emp e
    left join dept d
    on e.dept_id = d.id and d.name = '教研部';

说明:

上面这条 SQL 中,left join 会保留左表 emp 的所有记录。

如果某个员工所在部门不是"教研部",那么右表 dept 对应字段会显示为 NULL,但员工记录仍然会保留。

1.3 HAVING 子句

HAVING 用于分组之后再过滤分组结果,通常与 GROUP BY 一起使用。

适用场景:

  • 对分组统计结果进行筛选
  • 使用聚合函数作为条件
  • 例如筛选"人数大于 10 的部门"

特点:

  • 过滤的是"分组后的结果"

  • 可以使用聚合函数

  • 一般配合 GROUP BY 使用

    复制代码
    -- 查询人数超过 10 人的部门
    select d.name, count(*) as 人数
    from emp e
    join dept d on e.dept_id = d.id
    group by d.name
    having count(*) > 10;

1.4 三者对比

子句 作用阶段 过滤对象 是否可使用聚合函数 典型用途
WHERE 分组前 原始数据行 过滤基础数据
ON 连接时 表之间的匹配关系 一般不用于聚合条件 指定连接条件
HAVING 分组后 分组结果 过滤聚合后的结果

1.5 ON 和 WHERE 在外连接中的区别

ON 和 WHERE 在内连接中很多时候效果相近,但在外连接中差别非常明显。

写法一:条件写在 ON 中

复制代码
 select *
  from emp e
  left join dept d
  on e.dept_id = d.id and d.name = '教研部';

这条语句会保留 emp 表中的所有员工。

如果员工所在部门不是"教研部",右表字段显示为 NULL。

写法二:条件写在 WHERE 中

复制代码
  select *
  from emp e
  left join dept d
  on e.dept_id = d.id
  where d.name = '教研部';

这条语句会先完成左外连接,再通过 WHERE 过滤掉 d.name 不等于"教研部"的记录。

由于 NULL 不满足条件,最终效果往往接近内连接。

结论:

  • ON 更适合写连接条件

  • WHERE 更适合写结果过滤条件

  • 在外连接中,条件放在 ON 还是 WHERE,会直接影响最终结果

1.6 SQL 的常见执行顺序

虽然 SQL 的书写顺序是 SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY,

但数据库的常见执行逻辑通常是下面这样的:

  1. FROM

  2. JOIN

  3. ON

  4. WHERE

  5. GROUP BY

  6. HAVING

  7. SELECT

  8. ORDER BY

  9. LIMIT


示例结构如下:

select 选择的列

from 表名

join 另一张表 on 连接条件

where 行级过滤条件

group by 分组字段

having 分组过滤条件

order by 排序字段

limit 限制数量;


理解这个顺序后,就更容易明白:

  • 为什么 WHERE 不能直接过滤聚合结果

  • 为什么 HAVING 要放在 GROUP BY 后面

  • 为什么外连接中 ON 和 WHERE 的作用不同

2 GROUP BY 的使用

GROUP BY 用于将数据按照某个字段或多个字段进行分组,通常与聚合函数一起使用。

2.1 基本用法

按部门分组,统计每个部门的人数和平均工资

复制代码
  select dept_id, count(*) as 人数, avg(salary) as 平均工资
  from emp
  group by dept_id;

2.2 GROUP BY 的规则

  1. SELECT 中出现的非聚合列,通常都应该写在 GROUP BY 中。

正确:dept_id 和 name 都参与分组

复制代码
  select dept_id, name, count(*)
  from emp
  group by dept_id, name;

错误:name 没有出现在 group by 中

复制代码
  select dept_id, name, count(*)
  from emp
  group by dept_id;

说明:

不同数据库对这类写法的容忍度不同,但在规范写法中,非聚合字段应当出现在 GROUP BY 中。

2.3 GROUP BY 可以按照多个字段分组。

按部门和性别分组

复制代码
 select dept_id, gender, count(*)
  from emp
  group by dept_id, gender;

2.4 GROUP BY 与聚合函数

分组统计通常会搭配聚合函数一起使用,常见聚合函数如下:

函数 作用 示例
COUNT() 统计行数 count(*)
SUM() 求和 sum(salary)
AVG() 求平均值 avg(salary)
MAX() 求最大值 max(salary)
MIN() 求最小值 min(salary)

总结

今天我们学习了一堆SQL语句,真的是累炸了。后面我们将继续学习crud的操作

相关推荐
徐某人..1 小时前
基于i.MX6ULL平台的智能网关系统开发
arm开发·c++·单片机·qt·物联网·学习·arm
AOwhisky1 小时前
Kubernetes 学习笔记:集群管理、命名空间与 Pod 基础
linux·运维·笔记·学习·云原生·kubernetes
Navicat中国2 小时前
使用 Navicat 导入向导导入 Excel 数据时,系统提示导入成功,表中也能看到数据,但行数统计显示为 0,这是什么原因?
数据库·excel·导入
gmaajt2 小时前
Golang怎么做国际化多语言_Golang i18n教程【核心】
jvm·数据库·python
光影少年2 小时前
大屏页面,一次多个请求,请求加密导致 点击 全局时间选择器 时出现卡顿咋解决(面板收起会延迟1~2秒)
前端·javascript·vue.js·学习·前端框架·echarts·reactjs
折哥的程序人生 · 物流技术专研2 小时前
从“卡死”到“秒过”:WMS销售数据跨库回填的极限优化之旅
数据库·机器学习·oracle
李可以量化2 小时前
DeepSeek 量化交易实战:用标准化提示词模板实现 AI 辅助交易决策
大数据·数据库·人工智能
maqr_1102 小时前
CSS如何利用Sass定义全局阴影方案_通过变量实现统一CSS风格
jvm·数据库·python
m0_613856292 小时前
uni-app怎么做类似于美团的商家评价星级 uni-app五星评分组件制作【实战】
jvm·数据库·python