赞
踩
在实际开发中,需要根据实体的内容设计数据表,实体间会有各种关联关系。所以根据实体设计的数据表之间也存在着各种关联关系,MySQL中数据表的关联关系有三种。
多对一是数据表中最常见的一种关联关系。例如部门和员工之间的关系,一个部门可以有多个员工,而一个员工不能属于多个部门,也就是说部门表中的一行在员工表中可以有许多匹配行,但员工表中的一行在部门表中只能有一个匹配行。
表之间的关系是由外键建立的。在多对一的表关系中,应该将外键建立在多的一方,否则会造成数据的冗余。
注意:可以在多的一方设置外键保存另外一方的主键。
多对多也是数据表中一种关系,比如学生和课程的关系,一个学生可以选择多门课程,当然一门课程也可以供多个学生选择,即学生表中的一行在课程表中可以有许多匹配行,课程表中的一行在学生表中也有许多匹配行。
通常情况下,为了实现这种关系,需要定义一张中间表,称为连接表,该表会存在两个外键,分别参照课程表和学生表。在多对多的关系中,需要注意:连接表的两个外键都是可以重复的,但是两个外键之间的关系是不能重复的,所以这两个外键又是连接表的联合主键。
实际生活中,学生和老师的关系,也属于多对多。
一对一关系在实际生活中比较常见,例如人与身份证之间的关系就是一对一的关系,一个人对应一张身份证,一张身份证只能匹配一个人。如何在数据表中建立这种关联关系呢?
首先,要分清主从关系,从表需要主表的存在才有意义,身份证需要人的存在才有意义,因此人为主表,身份证为从表。要在身份证表中建立外键。由实际经验克制,身份证中的外键必须是非空唯一的,因此通常会直接用从表(身份证表)中的主键作为外键。
一对一的关系,主要可以运用于以下几个方面。
1, 分割具有很多列的表
2, 由于安全原因而隔离表的一部分
3, 保存临时的数据,并且可以毫不费力的通过删除该表而删除这些数据。
mysql> create table grade (
-> id int(4) not null primary key,
-> name varchar(36)
-> );
Query OK, 0 rows affected (0.56 sec)
mysql> create table student (
-> sid int(4) not null primary key,
-> sname varchar(36),
-> gid int(4) not null
-> );
Query OK, 0 rows affected (0.41 sec)
首先在表student和表grade中添加外键约束来建立两个表的关联关系。关于外键的定义、作用、添加和删除可以参见博客
mysql> desc grade -> ; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(4) | NO | PRI | NULL | | | name | varchar(36) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 2 rows in set (0.00 sec) mysql> desc student; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | sid | int(4) | NO | PRI | NULL | | | sname | varchar(36) | YES | | NULL | | | gid | int(4) | NO | | NULL | | +-------+-------------+------+-----+---------+-------+ rows in set (0.00 sec)
mysql> alter table student add constraint FK_ID foreign key(gid) references gra Query OK, 0 rows affected (1.31 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> desc grade; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(4) | NO | PRI | NULL | | | name | varchar(36) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 2 rows in set (0.00 sec) mysql> desc student; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | sid | int(4) | NO | PRI | NULL | | | sname | varchar(36) | YES | | NULL | | | gid | int(4) | NO | MUL | NULL | | +-------+-------------+------+-----+---------+-------+ 3 rows in set (0.00 sec)
上述代码片段表名已经建立了student表和grade表的关联关系并且已经成功添加外键约束。
mysql> show create table student;
+---------+--------------------------------------------------------------
| Table | Create Table
+---------+--------------------------------------------------------------
| student | CREATE TABLE `student` (
`sid` int(4) NOT NULL,
`sname` varchar(36) DEFAULT NULL,
`gid` int(4) NOT NULL,
PRIMARY KEY (`sid`),
KEY `FK_ID` (`gid`),
CONSTRAINT `FK_ID` FOREIGN KEY (`gid`) REFERENCES `grade` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+---------+--------------------------------------------------------------
row in set (0.00 sec)
从查询结果看,student表的外键约束已经成功添加。此时表student和表grade之间是多对一的关系。
因此外键列只能插入参考列存在的值,所以要外两个表添加数据,就需要先为主表grade添加数据,具体如下:
mysql> insert into grade (id, name) values(1, "软件一班");
Query OK, 1 row affected (0.16 sec)
mysql> insert into grade (id, name) values(2, "软件二班");
Query OK, 1 row affected (0.16 sec)
mysql> select * from grade;
+----+--------------+
| id | name |
+----+--------------+
| 1 | 软件一班 |
| 2 | 软件二班 |
+----+--------------+
3 rows in set (0.00 sec)
可以看到在grade班级表中已经插入了两条数据,添加的主键id为1和2,由于student表的外键与grade表的主键关联,因此在student表中添加数据时,gid的值只能是1或2,不能用其他的值。
mysql> insert into student(sid, name, gid) values(1, "王红", 1); ERROR 1054 (42S22): Unknown column 'name' in 'field list' mysql> insert into student(sid, sname, gid) values(1, "李青", 1); Query OK, 1 row affected (0.12 sec) mysql> insert into student(sid, sname, gid) values(1, "李青", 1); ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY' mysql> insert into student(sid, sname, gid) values(2, "王红", 1); Query OK, 1 row affected (0.21 sec) mysql> insert into student(sid, sname, gid) values(3, "赵云", 2); Query OK, 1 row affected (0.09 sec) mysql> insert into student(sid, sname, gid) values(4, "马云", 2); Query OK, 1 row affected (0.12 sec) mysql>
如果插入了grade表中不存在id值,报错如下
mysql> insert into student(sid, sname, gid) values(3, "李云龙", 5);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`student`, CONSTRAINT `FK_ID` FOREIGN KEY (`gid`) REFERENCES `grade` (`id`))
mysql>
上述的代码片段中两个表之间的数据已经有了关联性。例如要查询软件一班有哪些学生,需要先查找软件一班的id,然后根据这个id查询该班级中有哪些学生。具体步骤如下:
mysql> select id from grade where name="软件一班"; //MySQL支持单引号和双引号中文
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
然后再student表中查询gid=1的学生,即软件一班的学生。
mysql> select sname from student where gid=1;
+--------+
| sname |
+--------+
| 李青 |
| 王红 |
+--------+
2 rows in set (0.05 sec)
上一部分简单阐述了如何为关联表添加数据,在某些情况下还需要删除关联表中的数据。例如学校的软件一班取消了,就需要在数据库中将该班级以及该班级的学生一起删除。由于grade表和student表中存在关联关系,参照列被参照的的值不能被删除,因此在删除软件1班时,一定要先删除该班级的所有学生,然后再删除班级。如果直接删除grade表中仍存在参照关系的记录时报错如下:
mysql> delete from grade where id=1;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`test`.`student`, CONSTRAINT `FK_ID` FOREIGN KEY (`gid`) REFERENCES `grade` (`id`))
正确删除过程如下:
mysql> select * from student;
+-----+--------+-----+
| sid | sname | gid |
+-----+--------+-----+
| 1 | 李青 | 1 |
| 2 | 王红 | 1 |
| 3 | 赵云 | 2 |
| 4 | 马云 | 2 |
+-----+--------+-----+
4 rows in set (0.00 sec)
mysql> delete from student where gid = 1;
Query OK, 2 rows affected (0.15 sec)
mysql> delete from grade where id=1;
Query OK, 1 row affected (0.19 sec)
上述过程为正确的删除关联表的过程。
注意:在两个具有关联关系的表中删除数据时,一定要先删除从表中的数据,然后删除主表中的数据,否则会报错。
另外,在实际情况中,想要删除”软件一班”,并不需要删除”软件一班”的学生,可以将表student中”李青”,”王红”的gid改为NULL,只要主表中该列没有被参照就可以删除。但是由于在建表时,gid字段有非空约束,所以就只能将”李青”,”王红”的记录删除。
在关系型数据库中,建立表时各个数据之间的关系不必确定,通常将每个实体的所有信息存放在一个表中,当查询数据时,通过连接操作查询多个表中的实体信息,当两个或多个表中存在相同意义的字段时,便可以通过这些字段对不同的表进行连接查询,连接查询有如下四种:
交叉连接返回结果是被连接的两个表中的所有数据行的笛卡儿积,也就是返回第一个表中符合查询条件的数据行乘以第二个表中符合查询条件的数据行数。
语法格式如下:
select * from 表1 cross join 表2;
cross join用于连接连个要查询的表。
mysql> create table department(
-> did int(4) not null primary key,
-> dname varchar(40)
-> );
Query OK, 0 rows affected (0.41 sec)
mysql> create table employee(
-> id int(4) not null primary key,
-> name varchar(36),
-> age int(2),
-> did int(4) not null
-> );
Query OK, 0 rows affected (0.32 sec)
mysql> insert into department(did, dname) values (1, '网络部'); Query OK, 1 row affected (0.13 sec) mysql> insert into department(did, dname) values (2, '媒体部'); Query OK, 1 row affected (0.06 sec) mysql> insert into department(did, dname) values (3, '研发部'); Query OK, 1 row affected (0.06 sec) mysql> insert into department(did, dname) values (4, '人事部'); mysql> insert into employee(id, name, age, did) values (1, '王红', 20, 1); Query OK, 1 row affected (0.19 sec) mysql> insert into employee(id, name, age, did) values (2, '李强', 22, 1); Query OK, 1 row affected (0.07 sec) mysql> insert into employee(id, name, age, did) values (3, '赵四', 20, 2); Query OK, 1 row affected (0.06 sec) mysql> insert into employee(id, name, age, did) values (4, '郝娟', 20, 4); Query OK, 1 row affected (0.11 sec)
上述代码片段分别插入了部门和员工表,每张表插入了几条记录,注意两张表并没有外键约束。
mysql> select * from department cross join employee; +-----+-----------+----+--------+------+-----+ | did | dname | id | name | age | did | +-----+-----------+----+--------+------+-----+ | 1 | 网络部 | 1 | 王红 | 20 | 1 | | 2 | 媒体部 | 1 | 王红 | 20 | 1 | | 3 | 研发部 | 1 | 王红 | 20 | 1 | | 4 | 人事部 | 1 | 王红 | 20 | 1 | | 1 | 网络部 | 2 | 李强 | 22 | 1 | | 2 | 媒体部 | 2 | 李强 | 22 | 1 | | 3 | 研发部 | 2 | 李强 | 22 | 1 | | 4 | 人事部 | 2 | 李强 | 22 | 1 | | 1 | 网络部 | 3 | 赵四 | 20 | 2 | | 2 | 媒体部 | 3 | 赵四 | 20 | 2 | | 3 | 研发部 | 3 | 赵四 | 20 | 2 | | 4 | 人事部 | 3 | 赵四 | 20 | 2 | | 1 | 网络部 | 4 | 郝娟 | 20 | 4 | | 2 | 媒体部 | 4 | 郝娟 | 20 | 4 | | 3 | 研发部 | 4 | 郝娟 | 20 | 4 | | 4 | 人事部 | 4 | 郝娟 | 20 | 4 | +-----+-----------+----+--------+------+-----+ 16 rows in set (0.00 sec)
从结果看,交叉连接的结果就是两个表中所有数据的组合,但这在实际业务中这种需求很少,一般不会使用交叉连接,而是会使用具体的条件对数据进行有目的的查询。因为交叉连接产生的结果存在许多没有意义的数据。例如上述文档的标红处
内连接(Inner Join)又称简单连接或自然连接,是一种常见的连接查询。内连接使用比较运算符对两个表中的数据进行比较,并列出与连接条件匹配的数据行,组合成新的纪录。也就是说在内连接查询中,只有满足条件的记录才能出现在查询结果中。
内连接查询语法:
select 查询字段from 表1 [inner] join 表2 on 表1.关系字段 = 表2.关系字段
对department和employee两张表使用内连接查询
mysql> select employee.name, department.dname from department join employee on department.did=employee.did; +--------+-----------+ | name | dname | +--------+-----------+ | 王红 | 网络部 | | 李强 | 网络部 | | 赵四 | 媒体部 | | 郝娟 | 人事部 | +--------+-----------+ 4 rows in set (0.00 sec) mysql> select employee.name, department.dname from department inner join employee on department.did=employee.did; +--------+-----------+ | name | dname | +--------+-----------+ | 王红 | 网络部 | | 李强 | 网络部 | | 赵四 | 媒体部 | | 郝娟 | 人事部 | +--------+-----------+ 4 rows in set (0.00 sec) mysql>
可以明显看到内连接会过滤掉许多无用的数据,而留下真实有意义的数据。
注意:在使用inner join时,对于关系字段使用ON关键字而不是用where字段。
然而在MySQL中,可以使用where条件语句来实现相同的功能:
mysql> select employee.name, department.dname from department, employee where department.did=employee.did;
+--------+-----------+
| name | dname |
+--------+-----------+
| 王红 | 网络部 |
| 李强 | 网络部 |
| 赵四 | 媒体部 |
| 郝娟 | 人事部 |
+--------+-----------+
4 rows in set (0.00 sec)
mysql>
虽然where子句和inner join的查询结果是一样的,但是inner join是内连接语句,where是条件判断语句,在where语句后可以直接添加其他条件,而inner join 不可以。
mysql> select employee.name, department.dname from department inner join employee on department.did=employee.did && name="王红";
+--------+-----------+
| name | dname |
+--------+-----------+
| 王红 | 网络部 |
+--------+-----------+
1 row in set (0.00 sec)
如果在一个连接查询中,涉及的两个表是同一个表,这种查询称为自连接查询。自连接是一种特殊的内连接,它是指相互连接的表再物理上为同一个表,但逻辑上分为两张表。例如要查询王红所在的部门有哪些员工,就可以使用自连接查询,如下:
mysql> select p1.* from employee p1 join employee p2 on p1.did=p2.did where p2.name='王红';
+----+--------+------+-----+
| id | name | age | did |
+----+--------+------+-----+
| 1 | 王红 | 20 | 1 |
| 2 | 李强 | 22 | 1 |
+----+--------+------+-----+
2 rows in set (0.00 sec)
mysql>
详细的了解一下上述执行的过程,由于where子句为过滤,可以暂时不提,由于select子句选择的微p1.*,不够具体,我们可以把所有符合的内容都打印出来,如下:
mysql> select p1.*, p2.* from employee p1 join employee p2 on p1.did=p2.did
-> ;
+----+--------+------+-----+----+--------+------+-----+
| id | name | age | did | id | name | age | did |
+----+--------+------+-----+----+--------+------+-----+
| 1 | 王红 | 20 | 1 | 1 | 王红 | 20 | 1 |
| 2 | 李强 | 22 | 1 | 1 | 王红 | 20 | 1 |
| 1 | 王红 | 20 | 1 | 2 | 李强 | 22 | 1 |
| 2 | 李强 | 22 | 1 | 2 | 李强 | 22 | 1 |
| 3 | 赵四 | 20 | 2 | 3 | 赵四 | 20 | 2 |
| 4 | 郝娟 | 20 | 4 | 4 | 郝娟 | 20 | 4 |
+----+--------+------+-----+----+--------+------+-----+
6 rows in set (0.00 sec)
mysql>
加入where之后的条件,结果如下:
mysql> select p1.*, p2.* from employee p1 join employee p2 on p1.did=p2.did where p2.name='王红';
+----+--------+------+-----+----+--------+------+-----+
| id | name | age | did | id | name | age | did |
+----+--------+------+-----+----+--------+------+-----+
| 1 | 王红 | 20 | 1 | 1 | 王红 | 20 | 1 |
| 2 | 李强 | 22 | 1 | 1 | 王红 | 20 | 1 |
+----+--------+------+-----+----+--------+------+-----+
4 rows in set (0.00 sec)
过滤不要的p2.*
mysql> select p1.* from employee p1 join employee p2 on p1.did=p2.did where p2.name='王红';
+----+--------+------+-----+
| id | name | age | did |
+----+--------+------+-----+
| 1 | 王红 | 20 | 1 |
| 2 | 李强 | 22 | 1 |
+----+--------+------+-----+
2 rows in set (0.00 sec)
mysql>
结果如上所示,王红所在的部门有两个人,王红和李强。
在内连接查询中,返回的结果只包含符合查询条件和连接条件的数据,然而有时还需要包含没有关联的数据,即返回结果不仅包含符合条件的数据,而且还包含左表(左连接,左外连接)或右表(右连接或右外连接)或两个表(全外连接)中的所有数据,此时就要使用外连接查询。
外连接语法格式如下:
select 所查字段 from 表1 left | right [outer] join 表2 on 表1.关系字段=表2.关系字段 where 条件
注意使用left join, right join关键字左边的表称为左表,关键字右边的表称作右表。
left join: 返回左表中的所有记录和右表中符合连接条件的记录
right join: 返回包括右表中所有记录和左表中符合连接条件的记录。
mysql> select department.did, department.dname, employee.name from department left join employee on department.did=employee.did; +-----+-----------+--------+ | did | dname | name | +-----+-----------+--------+ | 1 | 网络部 | 王红 | | 1 | 网络部 | 李强 | | 2 | 媒体部 | 赵四 | | 4 | 人事部 | 郝娟 | | 3 | 研发部 | NULL | +-----+-----------+--------+ 4 rows in set (0.00 sec) mysql> select department.did, department.dname, employee.name from department join employee on department.did=employee.did; +-----+-----------+--------+ | did | dname | name | +-----+-----------+--------+ | 1 | 网络部 | 王红 | | 1 | 网络部 | 李强 | | 2 | 媒体部 | 赵四 | | 4 | 人事部 | 郝娟 | +-----+-----------+--------+ 4 rows in set (0.00 sec) mysql>
上述的代码片段即包含外连接和内连接,可以看到外连接比内连接多包含的一条记录,而该记录挺特殊的,为缺少的部门,对应的员工信息均为NULL
mysql> select * from department join employee on department.did=employee.did; +-----+-----------+----+--------+------+-----+ | did | dname | id | name | age | did | +-----+-----------+----+--------+------+-----+ | 1 | 网络部 | 1 | 王红 | 20 | 1 | | 1 | 网络部 | 2 | 李强 | 22 | 1 | | 2 | 媒体部 | 3 | 赵四 | 20 | 2 | | 4 | 人事部 | 4 | 郝娟 | 20 | 4 | +-----+-----------+----+--------+------+-----+ 4 rows in set (0.00 sec) mysql> select * from department left join employee on department.did=employee.did; +-----+-----------+------+--------+------+------+ | did | dname | id | name | age | did | +-----+-----------+------+--------+------+------+ | 1 | 网络部 | 1 | 王红 | 20 | 1 | | 1 | 网络部 | 2 | 李强 | 22 | 1 | | 2 | 媒体部 | 3 | 赵四 | 20 | 2 | | 4 | 人事部 | 4 | 郝娟 | 20 | 4 | | 3 | 研发部 | NULL | NULL | NULL | NULL | +-----+-----------+------+--------+------+------+ 5 rows in set (0.00 sec)
上述代码片段的实际意义,比如说我要查询每个部门的员工信息,这需要一种类似连接表的结构,即表中应该有部门id, 部门名称,以及对应的每个部门所存在的员工姓名,员工id,但是某个部门可能暂时没有员工,但我依然想要把该部门信息查询出来,只不过对应的员工信息均为空,即不存在员工罢了。
右连接与左连接正好相反,返回右表中所有指定的记录和所有满足连接条件的记录,如果右表的某条记录在左表中没有匹配,则左表将返回空值。
mysql> select department.did, department.dname, employee.name from department right join employee on department.did=employee.did; +------+-----------+--------+ | did | dname | name | +------+-----------+--------+ | 1 | 网络部 | 王红 | | 1 | 网络部 | 李强 | | 2 | 媒体部 | 赵四 | | 4 | 人事部 | 郝娟 | +------+-----------+--------+ 4 rows in set (0.00 sec) //插入一条员工,但did在department中不存在 mysql> insert into employee(id, name, age, did) Values(5, '周杰伦', 30, 10); Query OK, 1 row affected (0.18 sec) mysql> select department.did, department.dname, employee.name from department right join employee on department.did=employee.did; +------+-----------+-----------+ | did | dname | name | +------+-----------+-----------+ | 1 | 网络部 | 王红 | | 1 | 网络部 | 李强 | | 2 | 媒体部 | 赵四 | | 4 | 人事部 | 郝娟 | | NULL | NULL | 周杰伦 | +------+-----------+-----------+ 5 rows in set (0.00 sec)
实际意义就是,该员工在当前状态下可能属于实习期,并未确定录入那个部门,仅仅是添加了一条员工记录,对应的部门did并未真实。
复合条件连接查询就是在连接查询的过程中,通过添加过滤条件来限制查询结果,使得查询结果更加精确。
在department和employee表使用内连接查询,并将查询结果按照年龄从大到小排序:
mysql> select employee.name, employee.age, department.dname from department join employee on department.did=employee.did; +--------+------+-----------+ | name | age | dname | +--------+------+-----------+ | 王红 | 20 | 网络部 | | 李强 | 22 | 网络部 | | 赵四 | 20 | 媒体部 | | 郝娟 | 20 | 人事部 | +--------+------+-----------+ 4 rows in set (0.00 sec) mysql> select employee.name, employee.age, department.dname from department join employee on department.did=employee.did order by age desc; +--------+------+-----------+ | name | age | dname | +--------+------+-----------+ | 李强 | 22 | 网络部 | | 王红 | 20 | 网络部 | | 赵四 | 20 | 媒体部 | | 郝娟 | 20 | 人事部 | +--------+------+-----------+ 4 rows in set (0.00 sec) mysql> select employee.name, employee.age, department.dname from department join employee on department.did=employee.did order by did desc; ERROR 1052 (23000): Column 'did' in order clause is ambiguous mysql>
上述代码片段中使用did排序二义性是因为查询出来的结果有两个did,如下所示:
mysql> select * from department join employee on department.did=employee.did order by age;
+-----+-----------+----+--------+------+-----+
| did | dname | id | name | age | did |
+-----+-----------+----+--------+------+-----+
| 1 | 网络部 | 1 | 王红 | 20 | 1 |
| 2 | 媒体部 | 3 | 赵四 | 20 | 2 |
| 4 | 人事部 | 4 | 郝娟 | 20 | 4 |
| 1 | 网络部 | 2 | 李强 | 22 | 1 |
+-----+-----------+----+--------+------+-----+
4 rows in set (0.00 sec)
mysql>
由于查询出来的结果字段名一行存在两个did,因此无法直接使用did进行排序,可以通过表名进行限定:
mysql> select * from department join employee on department.did=employee.did order by did;
ERROR 1052 (23000): Column 'did' in order clause is ambiguous
mysql> select * from department join employee on department.did=employee.did order by department.did;
+-----+-----------+----+--------+------+-----+
| did | dname | id | name | age | did |
+-----+-----------+----+--------+------+-----+
| 1 | 网络部 | 1 | 王红 | 20 | 1 |
| 1 | 网络部 | 2 | 李强 | 22 | 1 |
| 2 | 媒体部 | 3 | 赵四 | 20 | 2 |
| 4 | 人事部 | 4 | 郝娟 | 20 | 4 |
+-----+-----------+----+--------+------+-----+
4 rows in set (0.00 sec)
mysql>
使用复合条件查询的结果较为精确,符合实际需求
本文详细的讲述了在MySQL使用过程中的关联关系的定义,演示了关联表中数据的添加和删除的过程,并且对MySQL中容易让人困惑的内连接查询、外连接查询进行详细的讲述,通过代码演示,希望能够阐明关于连接查询的问题。
传智博客MySQL数据库入门
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。