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

数据库多表关系、查询与约束

引言

在数据库设计与开发中,很多业务场景 ,都需要通过多表协作实现。而要让多表 “有序配合”,就必须掌握表间关系维护、多表查询技巧和数据约束规则这三大核心能力。本文将从多表关系的建立入手,逐步深入讲解多表查询的常用方法,并补充数据约束的关键知识点,帮助读者构建完整的数据库多表操作知识体系。

多表关系

在数据库的多表查询中,维护表之间的关系主要依靠表间关联(如一对多、多对多等)和SQL 的连接查询语法来实现。

  1. 一对多 案例:如部门(dept)与员工(emp)的关系(一个部门有多个员工,一个员工属于一个部门)。 维护方式在 “多” 的一方(员工表emp)建立外键,指向 “一” 的一方(部门表dept)的主键

  2. 多对多 如学生(student)与课程(course)的关系 维护方式:需通过中间表来实现,中间表包含两个外键,分别指向两个主表的主键。 例如:创建student_course中间表,包含student_id(关联student表的id)和course_id(关联course表的id)两个外键。 作用:通过中间表的外键约束,保证学生和课程的关联是合法的

  3. 一对一 案例:如用户(user)与用户详情(user_detail)的关系 维护方式:可在任意一方建立外键,且外键设置为唯一(UNIQUE); 例如:user_detail表的userId字段作为外键(且唯一),关联user表的id主键。

create table user_detail(
userId int unique comment '用户id',
constraint fk_userId foreign key(userId) references user(id)
);

多表查询

笛卡尔积

笛卡尔积(Cartesian Product)是数学中的一个概念,SQL 正是从数学里借用过来的。
给定两个集合 A 和 B,它们的笛卡尔积是:
A × B = {(a, b) | a ∈ A, b ∈ B}
即:把 A 的每个元素和 B 的每个元素配成一对。

在 SQL 里,如果你写:
SELECT *
FROM TableA, TableB;
而 不加任何 WHERE 条件,
数据库就会执行笛卡尔积操作,也就是“把 A 表的每一行和 B 表的每一行组合在一起”。

假设有两张表:表 A(学生):

idname
1张三
2李四

表 B(课程):

idcourse
101数学
102英语
103计算机

执行select * from A,B;后,结果为:

A.idA.nameB.idB.course
1张三101数学
1张三102英语
1张三103计算机
2李四101数学
2李四102英语
2李四103计算机

💡 一共产生了 2 × 3 = 6 行。这就是典型的笛卡尔积:每个学生都和每一门课程匹配一次。注意:真实场景中很少直接用笛卡尔积(会产生大量无意义数据),需通过条件过滤出有效关联。

内连接

内连接:相当于查询两个表(如 A 和 B)的交集部分数据,只有当两个表中存在匹配的记录时,才会返回结果。

笛卡尔积 + WHERE 条件 → 隐式内连接(JOIN)

笛卡尔积 + WHERE 条件 → 连接(JOIN)
在真实查询中,我们不会直接使用笛卡尔积,因为它会产生很多无意义的组合。
我们通常会加上条件来筛选出“有关联的行”。
例如,假设我们要找选课表:


这时,WHERE 条件就起到了“过滤笛卡尔积”的作用,
只留下真正相关的匹配行。
这其实就是 内连接(INNER JOIN) 的原始形式。

显式内连接(INNER JOIN 语法)
除了隐式写法,也可使用 inner join ... on... 明确表示内连接,可读性更强:

外连接

左外连接:查询左表(如 A)的所有数据,以及两张表的交集部分数据。即使右表(如 B)中没有匹配的记录,左表的记录也会被返回,右表对应字段显示为NULL。

右外连接:查询右表(如 B)的所有数据,以及两张表的交集部分数据。即使左表(如 A)中没有匹配的记录,右表的记录也会被返回,左表对应字段显示为NULL。

自连接

自连接:当前表与自身进行连接查询,必须使用表别名来区分同一个表的不同 “实例”。

自连接既可以是内连接,也可以是外连接,常用于处理表中存在 “层级关系” 或 “自身关联关系” 的场景

SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件...;。
例如:
select from emp a,emp b where a.managerid=b.id;

子查询

子查询是指在 SQL 语句中嵌套 SELECT 语句的查询方式,也称为嵌套查询。 其核心是用一个查询的结果作为另一个查询的条件或数据源,外部语句可以是INSERT、UPDATE、DELETE、SELECT中的任意一种。

按子查询位置分类

  • WHERE 之后:子查询作为筛选条件的一部分,如SELECT * FROM t1 WHERE column1 = (子查询)
  • FROM 之后:子查询结果作为 “临时表” 参与关联,如SELECT * FROM (子查询) AS temp JOIN t2 ON ...
  • SELECT 之后:子查询作为字段的一部分,如SELECT name, (SELECT COUNT(*) FROM t2 WHERE t2.id = t1.id) AS count FROM t1
标量子查询

子查询返回单个值

例子:查询在‘amaidou'入职之后的员工信息

先通过子查询获取 “amaidou” 的入职日期:select entrydate from emp where name = 'amaidou';
​
再以该日期为条件,查询入职更晚的员工:select * from emp where entrydate > (select entrydate from emp where name = 'amaidou');
列子查询

列子查询是子查询的一种类型,其返回结果为一列(可包含多行数据) 它常通过特定操作符实现复杂筛选,例如: int not in any 子查询返回列表中任意一个满足条件即可 SOME 与 ANY 等同,使用场景完全替代 ANY ALL 子查询返回列表的所有值都必须满足条件

1. ALL操作符(需满足子查询所有结果)

案例:查询比 “财务部” 所有人员工资都高的员工信息
===>
步骤 1:先获取财务部的所有工资:select salary from emp where dept_id = (select id from dept where name = '财务部');
步骤 2:用ALL筛选工资高于上述所有值的员工:select * from emp where salary > all ( select salary from emp where dept_id = (select id from dept where name = '财务部') );
说明:salary > all (...)表示员工工资需大于财务部所有人员的工资,只有同时满足 “比财务部最高工资还高” 的记录才会被选中

2. ANY操作符(只需满足子查询任意一个结果)

案例:查询比 “研发部” 任意一人工资高的员工信息
步骤 1:先获取研发部的所有工资:select salary from emp where dept_id = (select id from dept where name = '研发部');
步骤 2:用ANY筛选工资高于上述任意一个值的员工:select * from emp where salary > any ( select salary from emp where dept_id = (select id from dept where name = '研发部') );
说明:salary > any (...)表示员工工资只需大于研发部任意一人的工资(即只要比研发部最低工资高即可),就能被选中。
行子查询

其返回结果为一行(可包含多列数据),常用于多条件匹配的场景,常用操作符包括=、<> 、IN、NOT IN。

案例:查询与 “jay” 薪资及直属领导相同的员工信息

步骤一:获取jay的薪资和直属领导信息(行子查询的核心部分)
sql
select salary, managerid from emp where name = 'jay';
该子查询返回一行两列的结果(如(12500, 1)),代表jay的薪资和直属领导 ID。
步骤二:匹配与该结果相同的员工信息
sql
select * from emp where (salary,managerid) = (select salary, managerid from emp where name = 'jay');
这里通过(salary,managerid)多列匹配的方式,筛选出与jay “薪资和直属领导都相同” 的所有员工记录,体现了行子查询在多条件关联场景下的应用价值。
表子查询

子查询返回的结果是多行多列

案例:查询入职时间是'2021-02-07'之后的员工信息,及其部门信息

select e.*,d.* from (select * from emp where entrydate>'2021-02-07') e left join d on
e.dept_id=d.id;

这里将 “入职日期筛选后的员工子查询” 作为临时表e,再与部门表de

pt左连接,实现了 “筛选特定员工后,同时获取其部门信息” 的需求,体现了子查询与连接查询结合的灵活用法。

约束

  1. 非空约束(NOT NULL)

  2. 唯一约束(UNIQUE)作用:保证字段的值在表中是唯一的(但允许为NULL,且NULL可重复)。

  3. 主键约束(PRIMARY KEY)作用:是NOT NULL和UNIQUE的组合,用于唯一标识表中的每一条记录。

  4. 默认约束(DEFAULT)作用:为字段指定一个默认值,当插入记录时若未显式赋值,将使用该默认值。 gender char(1) default '男' comment '性别' -- 若插入时未指定性别,默认值为'男'

  5. 检查约束(CHECK)作用:通过自定义条件,限制字段值的范围(如年龄必须大于 0)。

create table user(
id int auto_increment primary key comment 'id唯一标识',
name varchar(10)not null unique  comment '姓名',
age int check(age>0&&age<=120) comment '年龄',
status char(1) default '1' comment '状态',
gender char(1) comment '性别'
);

外键约束

外键约束是用来让两张表的数据之间建立连接,从而保证数据的一致性和完整性。 子表(从表):具有外键 父表(主表):外键关联的表

语法:

1)创建表时添加外键:

CREATE TABLE 从表(字段名 数据类型,...[CONSTRAINT 外键名称] FOREIGN KEY (外键字段名) REFERENCES 主表(主表主键/唯一键字段名)
);

2)已有表添加外键:

ALTER TABLE 从表 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名) REFERENCES 主表(主表主键/唯一键字段名);
​
alter table emp add constraint fk_emp_dept foreign key(dept_id) references dept(id);
​
constraint fk_emp_dept 为外键指定了名称(规范命名通常以 fk_ 开头,体现从表和主表的关联);
foreign key(dept_id) 明确了从表(emp)的外键字段;
references dept(id) 正确关联了主表(dept)的主键字段(id)

3)删除外键

alter table 表名 drop foreign key 外键名称;

级联操作

外键可配置级联更新(ON UPDATE CASCADE)和级联删除(ON DELETE CASCADE),示例:

ALTER TABLE emp ADD CONSTRAINT fk_emp_dept 
FOREIGN KEY (dept_id) REFERENCES dept(id)
ON UPDATE CASCADE  -- 主表主键更新时,从表外键自动同步更新
ON DELETE CASCADE; -- 主表记录删除时,从表关联记录自动删除
​
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id) on update set null on delete set null ;
===>
on update set null:当主表(dept)的 id 被更新时,从表(emp)中所有关联的 dept_id 会被设为 NULL。
​
on delete set null:当主表(dept)的某条记录被删除时,从表(emp)中所有关联的 dept_id 会被设为 NULL。

注意事项:

1)主表的关联字段必须是主键或唯一键(保证值唯一,才能被从表引用)。
2)外键字段与主表关联字段的数据类型必须完全一致(如主表是int,外键也必须是int)。
3)并非所有场景都需外键:若系统通过业务逻辑即可保证数据一致性,也可不用(但外键能从数据库层面强制约束,更可靠)。

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

相关文章:

  • 空调设备公司网站建设建设部网站社保联网
  • 济源网站建设电话锦州做网站的个人
  • 逻辑推演题——谁是骗子
  • 单位网站建设汇报材料wordpress菜单保存不
  • 【底层机制】【Android】Android 系统的启动流程
  • js基础:06、函数(创建函数、参数、返回值、return、立即执行函数、对象(函数))和枚举对象的属性
  • LeetCode 刷题【131. 分割回文串】
  • 7. Functions(函数)
  • 零基础掌握 Vanna Text2SQL 框架:从原理到实战训练指南
  • [linux仓库]信号处理及可重入函数[进程信号·陆]
  • webrtc源码走读(一)-QOS-NACK-概述
  • wordpress 企业网站 免费如何注册网站免费的
  • 斗地主游戏源码,自适应手机版,带有管理后端
  • Linux桌面X11服务-XRecord方案捕获鼠标点击的应用窗口
  • 021数据结构之并查集——算法备赛
  • 网站制作售后免费在线代理网站
  • Vue组件的一些底层细节
  • 2. =>的用法 C#例子 WPF例子
  • 在C#中出现WinForm原控件Chart卡顿问题
  • Spring Boot 3零基础教程,WEB 开发 内嵌服务器底层源码分析 笔记48
  • 网站开发案例分析成都制作网页
  • 导入的 Google(Chrome)书签默认不会自动显示在「书签栏」,而是放在一个文件夹里。下面是详细步骤,帮你把 导入的全部书签添加到书签栏
  • 一小时内使用NVIDIA Nemotron创建你自己的Bash计算机使用智能体
  • Chrome开发者工具
  • 虚拟机 Ubuntu 中安装 Google Chrome 浏览器
  • Docker/K8s部署MySQL的创新实践与优化技巧大纲
  • 网站建设管理流程避免网站侵权
  • 如何在Visual Studio中配置C++环境?
  • 珠海翻译公司高效翻译服务 2025年10月
  • 网站后台管理系统怎么登陆鄂州网站建设与设计