前言
本篇博客將在上一篇的基礎上,繼續為大家梳理約束相關的知識,前面我們學習了Primary key和unique key方面的知識,本節我們專注於解決使用外鍵來定義表和表之間的三種關系:多對一,多對多,一對一。本次博客的內容比較重要,希望大家好好理解和記憶。
一.總體原則
判斷表與表之間的關系,我們主要通過三個步驟:
【001】1.解讀兩張表中的每條記錄代表什么意思,例如員工表中的一條記錄代表一個員工的信息,例如姓名,性別,年齡,所屬部門;
而部門表中的一條記錄代表某一個部門的信息,例如部門ID,部門名稱等;
【002】2.判斷左表的多條記錄是否可以關聯右表的一條記錄,同時判斷右表的多條記錄是否可以關聯左表的一條記錄
【003】3.如果判斷是多對一的關系,那么需要在這個基礎上考慮是否是一對一的關系,因此一對一實際上多對一的變種。
二.多對一:單向的foreign key
所謂多對一是指左表的多條記錄可以關聯右表的一條記錄;但是右表的多條記錄卻不能關聯左表的一條記錄。例如教師表和課程表,多個老師可以關聯一門課程,這就表示多個老師都可以教同一門課程,例如alex可以教Python,tom可以教Python,carson也可以教Python;但是多門課程卻不能關聯一個老師,畢竟術業有專攻嘛,不可能alex又教Python,又教Linux。所以教師表和課程表是多對一之間的關系。這個多指的是某張表中的多條記錄,既然是多條記錄,我們就需要使用foreign key來修飾。我們一起來實現剛才的例子:
# 創建課程表 多門課程不能關聯一個老師 CREATE TABLE course ( cid INT PRIMARY KEY AUTO_INCREMENT, cname VARCHAR(6) NOT NULL UNIQUE )AUTO_INCREMENT = 100; # 創建老師表 多個老師可以關聯一門課程,屬於多對一的關系 CREATE TABLE teacher ( tid INT PRIMARY KEY AUTO_INCREMENT, tname CHAR(10) NOT NULL , cid INT NOT NULL , (必須要定義,對應課程表中的cid) FOREIGN KEY(cid) REFERENCES course (cid) ON UPDATE CASCADE ON DELETE CASCADE )AUTO_INCREMENT = 10;
在上面教師表的定義中,我們使用foreign key定義了教師表和課程表之間的關系,最后使用
update cascade和delete cascade 定義了兩張表之間的更新和刪除關系:級聯更新和級聯刪除
這表明教師表中的cid字段跟隨着課程表中的cid字段所對應的記錄的更新而更新,刪除而刪除。
試想一下,例如alex教授Python這門課程,但是有一天課程表中講這條記錄刪除了,如果不設置
級聯刪除,那么課程表中不存在Python這門課程了,但是教師表中還存在,這豈不是很矛盾。同理
如果對課程表中的課程進行了修改,如果不設置級聯修改的話,那么教師表中的課程也不會有相應的更新
所以為了保證表與表之間的同步,必須設置級聯刪除和級聯修改。
另外還有一點要注意:既然兩表示多對一之間的關系,教師表中的cid字段關聯課程表中的cid字段
我們肯定要先創建課程表,然后再創建教師表進行關聯。
再例如員工表和部門表,多個員工記錄可以關聯部門表中的一條記錄,表示多個員工屬於相同的一個部門;但是多個部門記錄卻不能
關聯同一個員工,因為不可能一個員工你屬於多個部門;因為雖然有些人可能做的事跨部門,但是它畢竟還是屬於某一個部門。這里的
員工表和部門表之間的關系是多對一的關系。
在上面的例子中,我們判斷出教師表和課程表是多對一的關系,在此基礎上考慮兩者是否存在一對一之間的關系。由於教師表中的課程ID
不可能是唯一的,因為不可能存在每個老師教授的課程都是唯一的,只能是這樣的情況。例如alex,tom,carson教授python,Eva,Egon教授
雲計算,老男孩老師教授Linux;所以在教師表中的CID就對應多個課程ID。因此兩者不可能是一對一之間的關系。
三.一對一
在上面的例子中,我們談到如果兩張表的關系是多對一的關系,我們就要考慮兩者是否可能是一對一之間的關系。所謂一對一是指左表的一條記錄唯一對應右表的一條記錄。采用foreign key+unique實現。我們來看下面的例子:
以老男孩的教學為例,現在給定學生表和客戶表,學生表記錄着所以已經成為老男孩正式學員的學生,而客戶表意味着有潛在機會成為老男孩正式學員的人,就是說學生表中的人肯定都是由客戶演變過來的
。我們來嘗試分析下兩張表之間的關系,學生表中的多條記錄不能關聯客戶表中的一條記錄,因為不可能說客戶表中的一條記錄能夠演變為多個正式學員,只能是演變為一個學員;而客戶表中的多條記錄卻可以關聯
學生表中的一條記錄,因為可能存在說這5個客戶,最后只有一個成為老男孩的正式成員,其他四個由於各種原因都沒有成為老男孩的學員。分析到這里,我們可以斷定客戶表和學生表是多對一之間的關系。那我們接着分析
兩者是否可能是一對一的關系。對於客戶表而言,它里面的學生表ID是唯一的,不可能存在任意兩個學生的ID是相同的,所以分析到這里,兩者應該是一對一之間的關系。我們來看看它們的一個關系圖
如上圖所示,一個客戶演變為一個學生,然后在學生表的記錄中添加一個c_id即可。既然兩者是一對一之間的關系,那么具體在SQL語句上怎么設計呢?如下:
create table student( id int primary key auto_increment, name char(6), class_name char(10), c_id int unique, #保證學生表中的c_id是唯一的,由客戶演變為學生,所以是學生foreign key 客戶表
使用unique關鍵字來修飾外鍵,只有這樣才能保證是一對一之間的關系 foreign key(s_id) references student(id)
on update cascade
on delete cascade ); create table customer( # 客戶表只需要正常創建即可 id int primary key auto_increment, name char(6), phone int, qq char(11), mail varchar(20), );
四.多對多
所謂多對多是指雙向的foreign key,具體而言是說左表的多條記錄對應右表的一條記錄,而右表的多條記錄也能對應左表的一條記錄。我們還是通過實際的例子來說明:
需求:給定角色表和用戶表,因為每個用戶都有相應的角色,例如角色可以是管理員,教學主管,班主任等等。角色表中的多條記錄可以
關聯用戶表中的一條記錄,這就類似於一個人擁有多種角色,例如它既可以是管理員,又可以是教學主管;而用戶表中的多條記錄也可以
關聯角色表中的一條記錄,這就意味着多個用戶都可以是同一種角色,例如Alex,Egon,Tom這些人可以都是教學主管。這樣就形成了一個
雙向多對多的關系,所以兩者的關系是一個多對多的關系。
現在我們來考慮如何創建兩者表,前面我們說過當兩張表之間的關系是多對一時,例如教師表和課程表,肯定需要先創建多對一中的一,即
肯定需要先創建課程表,然后教師表中的cid才能foreign key到課程表中。但是現在是多對多之間的關系,這就陷入了一個雙向死循環,怎么辦?
我們的思路是創建一個第三方表來表示兩張表之間的關系,然后這兩張表直接按照正常創建即可,看如下的例子:
【001】創建用戶角色表:
CREATE TABLE role ( id INT PRIMARY KEY AUTO_INCREMENT, name CHAR(10), permission CHAR(3), COMMENT VARCHAR(10) );
【002】創建用戶表
CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, name CHAR(6), password VARCHAR(20) );
【003】既然是多對多的關系,雙方彼此關聯對方,因此需要創建一個第三張表來關聯這兩張表,如下所示:
CREATE TABLE user2role (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT, # 對應用戶表中的ID,相當於是外鍵
role_id INT, # 對應角色表中的ID,相當於是外鍵
FOREIGN KEY (user_id) REFERENCES user (id)
ON UPDATE CASCADE
ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES role (id)
ON UPDATE CASCADE
ON DELETE CASCADE
);
五.案例解析
前面我們詳細分析了使用外鍵來關聯兩張表之間的關系,這里我們通過幾個實際的例子來強化上面的知識點,一起來看看下面的幾個例子:
【001】
# 6.表結構設計(20分) # a)設計表 # 用戶表與部門表(一個用戶只能屬於一個用戶組)(6分) # 部門表與主機表(一個部門可以管理多台主機)(6分) # b)查詢 # 查詢技術部門的員工總數(2分) # 查詢技術部門管理的主機數(2分) # 查詢員工李平所在部門管理的主機信息(2分) # 查詢ip為192.168.45.10的主機所在部門的員工信息(2分) # 創建三張表:1.用戶表,2.部門表,3.主機表 # 多個用戶可以對應一個部門記錄,多個部門記錄不能關聯一個用戶,屬於多對一的關系 # 多台主機可以對應一個部門,多個部門可以關聯一台主機 屬於多對多的關系
我們一起來分析下用戶表,部門表,主機表三張表之間的關系:
【001】部門表的整體記錄可以看作是一個用戶組,多個用戶可以關聯部門表中的一條記錄,即這多個用戶屬於同一個部門,在同一個用戶組里面;
而部門表中的多條記錄卻不能關聯一個用戶,因為一個用戶它只能屬於一個用戶組,所以用戶表和部門表示多對一的關系;
【002】再來分析部門表和主機表之間的關系。部門表中的多條記錄可以關聯主機表中的一條記錄,因為一台主機是可以被多個部門使用的,這樣
做到了資源共享;主機表中的多條記錄是可以關聯部門表中的一條記錄的,即一個部門在使用主機表中的這些主機。因此部門表和主機表是
多對多的關系。明確了兩者之間的關系,我們可以開始來創建表:
# 創建用戶表 CREATE TABLE monthexam.user_info ( uid INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL, password VARCHAR(50) NOT NULL, d_id int, FOREIGN KEY(d_id) REFERENCES department(dept_id) ON UPDATE CASCADE ON DELETE CASCADE ); # 創建部門表 create table department( dept_id int PRIMARY KEY AUTO_INCREMENT, name varchar(20) NOT NULL ); # 創建主機表 CREATE TABLE host ( host_id int PRIMARY KEY AUTO_INCREMENT, ip CHAR(16) NOT NULL UNIQUE DEFAULT '127.0.0.1' )AUTO_INCREMENT=100; # 建立主機表和部門表多對多的關系 CREATE TABLE hostTodept( id INT PRIMARY KEY AUTO_INCREMENT, host_id INT NOT NULL , dept_id INT NOT NULL , FOREIGN KEY (host_id) REFERENCES host(host_id) ON UPDATE CASCADE ON DELETE CASCADE, FOREIGN KEY (dept_id) REFERENCES department(dept_id) ON UPDATE CASCADE ON DELETE CASCADE );
接下來我們插入數據:
# 為用戶表插入數據 INSERT INTO user_info (username, password, d_id) VALUES ('root', '123', 200), ('carson', '456', 201), ('李平', 'cisco',200), ('eva', '123456', 203), ('王五', '45678', 202), ('趙六', '666', 201), ('王依', '678910', 200), ('tom', '1234', 202); #為部門表插入數據 insert into department values (200,'技術'), (201,'人力資源'), (202,'銷售'), (203,'運營'); # 為主機表插入數據 INSERT INTO host (ip) VALUES ('172.16.41.3'), ('172.16.28.10'), ('172.16.44.3'), ('172.16.32.11'), ('172.10.45.3'), ('172.10.45.4'), ('192.168.45.10'), ('192.168.11.22'), ('192.168.21.23'), ('192.168.11.223'), ('192.168.11.24');
最后我們來完成上面的查詢需求:
# 查詢技術部門的員工總數 SELECT d.name as '部門', count(u.uid) as '員工總數' FROM department d LEFT JOIN user_info u ON d.dept_id = u.d_id where d.name='技術' GROUP BY d.dept_id; #查詢技術部門管理的主機數 SELECT d.name as '部門' , count(h.host_id) as '主機數' FROM department d INNER JOIN hostTodept h ON d.dept_id = h.dept_id WHERE d.name='技術'; # 查詢員工李平所在部門管理的主機信息 SELECT host_id as '主機ID', ip as '主機IP' FROM host where host_id IN ( SELECT host_id FROM hostTodept WHERE dept_id IN (SELECT d.dept_id FROM user_info u INNER JOIN department d ON u.d_id = d.dept_id where u.username='李平') ); # 查詢ip為192.168.45.10的主機所在部門的員工信息 SELECT * FROM user_info where d_id IN( SELECT ht.dept_id FROM host h INNER JOIN hostTodept ht ON h.host_id = ht.host_id WHERE h.ip='192.168.45.10' );
六.選課系統之表關系
我們首先來看如下這張選課系統之間的圖,我們嘗試來理清它們之間的關系:
上面主要存在五張表:學生表,班級表,老師表,課程表,成績表
學生和班級之間的關系:多個班級可以關聯一個學生(即一個學生可以參加不同的班級學習多門不同的課程,例如carson即可參加Linux班
的學習,又可以參加Python班級的學習);
多個學生可以關聯一個班級,這表示一個Python班級里面有多名學生;這又是一個多對多的關系
老師和課程之間的關系:多個老師可以教一門課程,多個課程不可以被一個老師教(術業有專攻)即一個老師不能教多門課程。
這個關系是多對一的關系,是老師關聯課程,意味着teacher表要添加一個字段:cid
班級和課程之間的關系:多個班級可以對應一門課程,例如7期python,6期python
多門課程不可以對應一個班級,即7期Python不能又教Python,還教Linux課程。因此這個屬於多對一的關系。即班級foreign key 課程
學生和課程之間的關系:多個學生可以選擇一門課程,多個課程可以關聯一個學生,這是一個多對多的關系
這里我們有點小問題,學生和課程之間不應該直接發生關系,而應該采用成績表來關聯。雖然學生表和課程表實際上是多對多的關系,但是
為了按照實際的需求,例如統計某個學生某門課程的分數,我們是應該創建一張成績表。
在本篇博客的最后,我們來一起復習下前面的set知識點:
# =========================練習枚舉類型 # 這里就規定了Tshirt的大小只能從枚舉類型中尋找 #enum 表示單選 只能在給定的范圍內選一個值,如性別 sex 男male/女female create table shirts( name VARCHAR(40), size ENUM('x-small', 'small', 'medium', 'large', 'x-large') DEFAULT 'Small' ); INSERT INTO shirts(name,size) VALUES ('dress shirts', 'large'), ('T-shirts', 'large'), ('medium', 'medium'), ('polo shirt', 'small'); SELECT * FROM shirts; #set 多選 在給定的范圍內可以選擇一個或一個以上的值(愛好1,愛好2,愛好3...) CREATE table myset(col SET('a', 'b', 'c', 'd')); INSERT INTO myset (col) VALUES ('a,d'), ('d,a'), ('a,d,a'), ('a,d,d'), ('d,a,d'); SELECT * FROM myset; # 再來進行枚舉示例 create table t1( id INT, name CHAR(10), sex ENUM('男', '女') # 你可以傳遞為空,但是存儲的默認值是 男 ); # 記住這里修改 sex的默認屬性時,需要加上ENUM ALTER table t1 MODIFY sex ENUM('男', '女') not NULL DEFAULT '男'; INSERT INTO t1 VALUES (1, 'alex', '男'); INSERT INTO t1(id, name) VALUES (2, 'carson'); SELECT * FROM t1; desc t1; # 下面來接着演示 set create table t2( id INT, name CHAR(10), hobbies SET('music', 'read', 'footbal', 'eat', 'sleep') ); INSERT INTO t2 VALUES (1, 'alex', 'music, read'); SELECT *