数据库篇-mysql详解( 五 )之外键与子查询

一 : 外键操作

外键: foreign key, 外面的键(键不在自己表中): 如果一张表中有一个字段(非主键)指向另外一张表的主键,那么将该字段称之为外键.

① : 增加外键

外键可以在创建表的时候或者创建表之后增加(但是要考虑数据的问题).

(1) : 在创建表的时候创建外键

一张表可以有多个外键
创建表的时候增加外键: 在所有的表字段之后,使用foreign key(外键字段) references 外部表(主键字段)
创建语句

create table my_foreign1(
id int primary key auto_increment,
name varchar(20) not null comment '学生姓名',
c_id int comment '班级id',    -- 普通字段
-- 增加外键
foreign key(c_id) references my_class(id)
)charset utf8;

表结构
外键 : 要求字段本身必须先是一个索引(普通索引),如果字段本身没有索引,外键会先创建一个索引,然后才会创建外键本身

+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| name  | varchar(20) | NO   |     | NULL    |                |
| c_id  | int(11)     | YES  | MUL | NULL    |                |
+-------+-------------+------+-----+---------+----------------+

查看创建语句
show create table my_foreign1

+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table       | Create Table                                                                                                                                                                                                                                                                                                                                       |
+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| my_foreign1 | CREATE TABLE `my_foreign1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL COMMENT '学生姓名',
  `c_id` int(11) DEFAULT NULL COMMENT '班级id',
  PRIMARY KEY (`id`),
  KEY `c_id` (`c_id`),
  CONSTRAINT `my_foreign1_ibfk_1` FOREIGN KEY (`c_id`) REFERENCES `my_class` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8       |
+-------------+----------------------------------------------------------------------

其中如果本身字段没有索引,外键创建索引 -> KEY 'c_id' ('c_id')
my_foreign1_ibfk_1 为外键的名字

( 2 ) : 在新增表之后增加外键,修改表结构

Alter table 表名 add [constraint 外键名字] foreign key(外键字段) references 父表(主键字段);

创建一个表

create table my_foreign2(
id int primary key auto_increment,
name varchar(20) not null comment '学生姓名',
c_id int comment '班级id' -- 普通字段
)charset utf8;

增加外键

-- 增加外键
alter table my_foreign2 add
-- 指定外键名
constraint student_class_1
-- 指定外键字段
foreign key(c_id)
-- 引用父表主键
references my_class(id);
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| name  | varchar(20) | NO   |     | NULL    |                |
| c_id  | int(11)     | YES  | MUL | NULL    |                |
+-------+-------------+------+-----+---------+----------------+

② : 修改与删除外键

外键不可修改: 只能先删除后新增.
Alter table 表名 drop foreign key 外键名; -- 一张表中可以有多个外键,但是名字不能相同

注 :外键删除不能通过查看表结构体现,应该通过查看表创建语句查看.

alter table my_foreign1 drop foreign key my_foreign1_ibfk_1;
+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table       | Create Table                                                                                                                                                                                                                                                  |
+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| my_foreign1 | CREATE TABLE `my_foreign1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL COMMENT '学生姓名',
  `c_id` int(11) DEFAULT NULL COMMENT '班级id',
  PRIMARY KEY (`id`),
  KEY `c_id` (`c_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8       |
+-------------+-------------------------------------

二 : 外键的作用

外键默认的作用有两点 :

① 对于子表约束 :

子表数据进行写操作(增和改)的时候, 如果对应的外键字段在父表找不到对应的匹配: 那么操作会失败.(约束子表数据操作)

insert into my_foreign2 values(null,'思思',4); -- 没有4班级
insert into my_foreign2 values(null,'可可',1); 
insert into my_foreign2 values(null,'娜娜',2); 
insert into my_foreign2 values(null,'雪芙',2); 

结果
父表中没有4班,所以插入不成功

+----+--------+------+
| id | name   | c_id |
+----+--------+------+
|  2 | 可可   |    1 |
|  3 | 娜娜   |    2 |
|  4 | 雪芙   |    2 |
+----+--------+------+
② 对于父表约束 :

对父表约束: 父表数据进行写操作(删和改: 都必须涉及到主键本身), 如果对应的主键在子表中已经被数据所引用, 那么就不允许操作

update my_class set id = 4 where id = 1; -- 失败: id=1记录已经被学生引用
update my_class set id = 4 where id = 3; -- 可以: 没有引用

查看结果

+----+---------+------+
| id | name    | room |
+----+---------+------+
|  1 | PHP0710 | A203 |
|  2 | PHP0810 | B205 |
|  4 | PHP0910 | C206 |

三 : 外键条件

① : 外键要存在 : 首先必须保证表的存储引擎是innodb(默认的存储引擎),如果不是innodb存储引擎,那么外键可以创建成功,但是没有约束效果.
② : 外键字段的字段类型(列类型)必须与父表的主键类型完全一致
③ : 一张表中的外键名字不能重复.
④ : 增加外键的字段(数据已经存在),必须保证与父表主键要求对应

新增加的外键的字段 3在父表中不存在,无法建立连接

insert into my_foreign1 values(null,'雨晴',3);

my_class表

+----+---------+------+
| id | name    | room |
+----+---------+------+
|  1 | PHP0710 | A203 |
|  2 | PHP0810 | B205 |
|  4 | PHP0910 | C206 |
+----+---------+------+
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`web13`.`#sql-66_90`, CONSTRAINT `#sql-66_90_ibfk_1` FOREIGN KEY (`c_id`) REFERENCES `my_class` (`id`))

四 : 外键约束

所谓外键约束: 就是指外键的作用.
上述外键的作用是默认的,其实可以通过对外键的需求,进行定制操作

三种约束模式 : 都是针对父表的约束

① : District 严格模式(默认的),父表不能删除或者更新一个已经被子表数据引用的记录.
② : Cascade 级联模式: 父表的操作, 对应子表关联的数据也跟着被删除
③ : Set null 置空模式: 父表的操作之后,子表对应的数据(外键字段)被置空

通常的一个合理的做法(约束模式): 删除的时候子表置空, 更新的时候子表级联操作

-- 创建外键: 指定模式: 删除置空,更新级联
create table my_foreign3(
id int primary key auto_increment,
name varchar(20) not null,
c_id int,
-- 增加外键
foreign key(c_id)
-- 引用表
references my_class(id)
-- 指定删除模式
on delete set null
-- 指定更新默认
on update cascade)charset utf8;

show create table my_foreign3;

+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table       | Create Table                                                                                                                                                                                                                                                                                                                                  |
+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| my_foreign3 | CREATE TABLE `my_foreign3` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `c_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c_id` (`c_id`),
  CONSTRAINT `my_foreign3_ibfk_1` FOREIGN KEY (`c_id`) REFERENCES `my_class` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------------+------------------------------------------

更新操作 : 级联更新

+----+--------+------+
| id | name   | c_id |
+----+--------+------+
|  1 | 思思   |    1 |
|  2 | 美美   |    1 |
|  3 | 晴晴   |    1 |
|  4 | 欣欣   |    2 |
|  5 | 娜娜   |    2 |
+----+--------+------+
+----+---------+------+
| id | name    | room |
+----+---------+------+
|  1 | PHP0710 | A203 |
|  2 | PHP0810 | B205 |
|  4 | PHP0910 | C206 |
+----+---------+------+

更新父表主键
update my_class set id = 3 where id = 1;
再次查看变化

+----+--------+------+
| id | name   | c_id |
+----+--------+------+
|  1 | 思思   |    3 |
|  2 | 美美   |    3 |
|  3 | 晴晴   |    3 |
|  4 | 欣欣   |    2 |
|  5 | 娜娜   |    2 |
+----+--------+------+

删除操作 : 置空

删除父表的主键

delete from my_class where id = 2;

查看字表的变化

+----+--------+------+
| id | name   | c_id |
+----+--------+------+
|  1 | 思思   |    3 |
|  2 | 美美   |    3 |
|  3 | 晴晴   |    3 |
|  4 | 欣欣   | NULL |
|  5 | 娜娜   | NULL |
+----+--------+------+

删除置空的前提条件: 外键字段允许为空(如果不满足条件,外键无法创建)

五 : 联合查询

联合查询 : 将多次查询(多条语句)在记录上进行拼接(字段不会增加)

Select 语句1
Union [union选项]
Select语句2...
Union选项: 与select选项一样有两个
All: 保留所有(不管重复)
Distinct: 去重(整个重复): 默认的
默认去重
-- 联合查询
select * from my_class
union -- 默认去重
select * from my_class;
结果
+----+---------+------+
| id | name    | room |
+----+---------+------+
|  3 | PHP0710 | A203 |
|  4 | PHP0910 | C206 |
+----+---------+------+
不去重
select * from my_class
union all -- 不去重
select * from my_class;
结果
+----+---------+------+
| id | name    | room |
+----+---------+------+
|  3 | PHP0710 | A203 |
|  4 | PHP0910 | C206 |
|  3 | PHP0710 | A203 |
|  4 | PHP0910 | C206 |
联合查询只要求字段数一样,跟数据类型无关
select id,c_name,room from my_class
union all -- 不去重
select name,number,id from my_student;
+--------+-----------+------+
| id     | name      | room |
+--------+-----------+------+
| 3      | PHP0710   | A203 |
| 4      | PHP0910   | C206 |
| 思思   | class0001 | 1    |
| 可可   | class0002 | 2    |
| 娜娜   | class0003 | 3    |
| 美美   | class0004 | 4    |
| 彩彩   | class0005 | 5    |
| 琪琪   | class0006 | 6    |
| 琪琪   | class0006 | 7    |
| 琪琪   | class0006 | 8    |
+--------+-----------+------+

联合查询的意义

  • 1.​查询同一张表,但是需求不同: 如查询学生信息, 男生身高升序, 女生身高降序.
  • 2.​多表查询: 多张表的结构是完全一样的,保存的数据(结构)也是一样的.
    多表查询增加效率,可以给十万量级的表进行分表,成万级别,再分表成千级别,进行联合查询,增加效率.
联合查询中 Order by使用

需求 : 要求男生升序,女生降序
注意 1 : 在联合查询中 : order by 不能直接使用,需要对查询语句使用括号才行

注意2 : 若要orderby生效: 必须搭配limit: limit使用限定的最大数即可.

(select * from my_student where sex = '男' order by age asc limit 9999999)
union 
(select * from my_student where sex = '女' order by age desc limit 9999999);
+----+-----------+--------+------+--------+------+------+
| id | number    | name   | sex  | height | c_id | age  |
+----+-----------+--------+------+--------+------+------+
|  5 | class0005 | 彩彩   | 男   |    166 |    3 |    7 |
|  2 | class0002 | 可可   | 男   |    182 |    1 |   10 |
|  1 | class0001 | 思思   | 男   |    184 |    3 |   16 |
|  4 | class0004 | 美美   | 男   |    172 |    3 |   18 |
|  7 | class0006 | 琪琪   | 女   |    168 |    2 |   24 |
|  3 | class0003 | 娜娜   | 女   |    156 |    1 |   19 |
|  6 | class0006 | 琪琪   | 女   |    168 |    2 |   19 |
|  8 | class0006 | 琪琪   | 女   |    168 |    2 |   16 |
+----+-----------+--------+------+--------+------+------+

六 : 子查询

子查询 : sub query,查询是在某个查询结果之上进行的.(一条select语句内部包含了另外一条select语句).

子查询有两种分类方式: 按位置分类; 按结果分类

按位置分类: 子查询(select语句)在外部查询(select语句)中出现的位置

  • From子查询: 子查询跟在from之后
  • Where子查询: 子查询出现where条件中
  • Exists子查询: 子查询出现在exists里面

按结果分类: 根据子查询得到的数据进行分类(理论上讲任何一个查询得到的结果都可以理解为二维表)

  • 标量子查询: 子查询得到的结果是一行一列
  • 列子查询: 子查询得到的结果是一列多行
  • 行子查询: 子查询得到的结果是多列一行(多行多列)
    上面几个出现的位置都是在where之后
    表子查询: 子查询得到的结果是多行多列(出现的位置是在from之后)
① : 标量子查询

需求: 知道班级名字为PHP0710,想获取该班的所有学生.

1. 确定数据源: 获取所有的学生
Select * from my_student where c_id = ?;
2. 获取班级ID: 可以通过班级名字确定
Select id from my_class where c_name = ‘PHP0710’;
select * from my_student where c_id = (select id from my_class where name = 'PHP0710');

或者

select * from my_student having c_id = (select id from my_class where c_name = 'PHP0710');
+----+-----------+--------+------+--------+------+------+
| id | number    | name   | sex  | height | c_id | age  |
+----+-----------+--------+------+--------+------+------+
|  1 | class0001 | 思思   | 男   |    184 |    3 |   16 |
|  4 | class0004 | 美美   | 男   |    172 |    3 |   18 |
|  5 | class0005 | 彩彩   | 男   |    166 |    3 |    7 |
+----+-----------+--------+------+--------+------+------+
② : 列子查询

需求: 查询所有在读班级的学生(班级表中存在的班级)

1. 确定数据源: 学生
Select * from my_student where c_id in (?);
2. 确定有效班级的id: 所有班级id
Select id from my_class;
select * from my_student where c_id in(select id from my_class);
+----+-----------+--------+------+--------+------+------+
| id | number    | name   | sex  | height | c_id | age  |
+----+-----------+--------+------+--------+------+------+
|  1 | class0001 | 思思   | 男   |    184 |    3 |   16 |
|  4 | class0004 | 美美   | 男   |    172 |    3 |   18 |
|  5 | class0005 | 彩彩   | 男   |    166 |    3 |    7 |
+----+-----------+--------+------+--------+------+------+

在mysql中有还有几个与in类似的条件: all, some, any

  • =Any ==== in; -- 其中一个即可
  • Any ====== some;​-- any跟some是一样
  • =all ==== 为全部
select * from my_student where c_id =any(select id from my_class);
mysql> select * from my_student where c_id =some(select id from my_class);

结果

+----+-----------+--------+------+--------+------+------+
| id | number    | name   | sex  | height | c_id | age  |
+----+-----------+--------+------+--------+------+------+
|  1 | class0001 | 思思   | 男   |    184 |    3 |   16 |
|  4 | class0004 | 美美   | 男   |    172 |    3 |   18 |
|  5 | class0005 | 彩彩   | 男   |    166 |    3 |    7 |
+----+-----------+--------+------+--------+------+------+
select * from my_student where c_id =all(select id from my_class);

结果

Empty set (0.00 sec)

否定结果

!=any 与 !=somec_id不等于 任意一个就成立,!=all全都不等于才成立

select * from my_student where c_id !=any(select id from my_class); -- 所有结果(null除外)
select * from my_student where c_id !=some(select id from my_class); -- 所有结果(null除外)
select * from my_student where c_id !=all(select id from my_class); -- 2(null除外)
③ : 行子查询

行子查询: 返回的结果可以是多行多列(一行多列)
需求: 要求查询整个学生中,年龄最大且身高是最高的学生.

1. 确定数据源
Select * from my_student where age = ? And height = ?;
2. 确定最大的年龄和最高的身高;
Select max(age),max(height) from my_student;
select * from my_student where 
-- (age,height)称之为行元素
(age,height) = (select max(age),max(height) from my_student);
④ : 表子查询

表子查询: 子查询返回的结果是多行多列的二维表: 子查询返回的结果是当做二维表来使用
需求: 找出每一个班最高的一个学生.

1. 确定数据源: 先将学生按照身高进行降序排序
Select * from my_student order by height desc;
2. 从每个班选出第一个学生
Select * from my_student group by c_id; -- 每个班选出第一个学生
select * from (select * from my_student order by height desc) as student group by c_id;
⑤Exists子查询

Exists: 是否存在的意思, exists子查询就是用来判断某些条件是否满足(跨表), exists是接在where之后: exists返回的结果只有0和1.

需求: 查询所有的学生: 前提条件是班级存在

  1. 确定数据源
    Select * from my_student where ?;
  2. 确定条件是否满足
    Exists(Select * from my_class); -- 是否成立
select * from my_student where 
exists(select * from my_class where id = 1);

你可能感兴趣的:(数据库篇-mysql详解( 五 )之外键与子查询)