文章目錄
- 數據庫約束
- NOT NULL(非空約束)
- UNIQUE(唯一約束)
- DEFAULT(缺省約束)
- PRIMARY KEY(主鍵約束)
- AUTO_INCREMENT 自增
- FOREIGN KEY(外鍵約束)
- CHECK(檢查約束)
- 表的設計
- 表的關系
- 一對一
- 一對多
- 多對多
- 三大范式
- 第一范式
- 第二范式
- 第三范式
數據庫約束
數據庫中主要有六種約束:
NOT NULL(非空約束)
- 指示某列不能存儲NULL
值。UNIQUE(唯一約束)
- 保證某列的每行必須有唯一的值。DEFAULT(缺省約束)
- 規定沒有給列賦值時的默認值。PRIMARY KEY(主鍵約束)
-NOT NULL
和UNIQUE
的結合。確保某列(或兩個列多個列的結合)有唯一標識,有助于更容易更快速地找到表中的一個特定的記錄。FOREIGN KEY(外鍵約束)
- 保證一個表中的數據匹配另一個表中的值的參照完整性。CHECK(檢查約束)
- 保證列中的值符合指定的條件。對于MySQL
數據庫,對CHECK
子句進行分析,但是忽略CHECK
子句。
NOT NULL(非空約束)
指示某列不能存儲 NULL 值。
mysql> create table book(-> id int,-> name varchar(12),-> price double,-> publish date,-> num int NOT NULL-> );
Query OK, 0 rows affected (0.02 sec)// 可以看到 num 的 NULL 屬性已經變為 NO(不允許)
mysql> DESC book;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| publish | date | YES | | NULL | |
| num | int | NO | | NULL | |
+---------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)// 嘗試一下插入一個 num 為 NULL 的數據
mysql> INSERT INTO book VALUES(2, "C++", 13.6, "2000-08-02", NULL);
ERROR 1048 (23000): Column 'num' cannot be null// 可以發現會報錯: num 列不可以為 NULL
UNIQUE(唯一約束)
保證某列的每行必須有唯一的值,即對于添加了唯一約束的數據項不能有重復。
mysql> create table book(-> id int UNIQUE,-> name varchar(12),-> price double,-> publish date,-> num int-> );
Query OK, 0 rows affected (0.03 sec)// 可以看到 id 的 key 屬性已經被標記為 UNI
mysql> desc book;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int | YES | UNI | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| publish | date | YES | | NULL | |
| num | int | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)mysql> INSERT INTO book VALUES(1, "C++", 13.5, NULL, 10);
Query OK, 1 row affected (0.00 sec)mysql> INSERT INTO book VALUES(1, "JAVA", 3.5, "2000-08-02", 5);
ERROR 1062 (23000): Duplicate entry '1' for key 'book.id'
// 雖然第二條數據除了 id,其他列的值都不同于第一條數據,然而還是會報錯:重復輸入“1”作為“book.id”的值
DEFAULT(缺省約束)
規定沒有給列賦值時的默認值。
mysql> create table book(-> id int,-> name varchar(12),-> price double,-> publish date DEFAULT "2000-08-02",-> num int-> );
Query OK, 0 rows affected (0.02 sec)// 可以看到 publish 的 Default 屬性被設置為 2000-08-02
mysql> desc book;
+---------+-------------+------+-----+------------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+------------+-------+
| id | int | YES | | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| publish | date | YES | | 2000-08-02 | |
| num | int | YES | | NULL | |
+---------+-------------+------+-----+------------+-------+
5 rows in set (0.01 sec)// 指定列插入,不包含 publish 列
mysql> INSERT INTO book(id, name, price, num) VALUES(3, "C++", 13.5, 15);
Query OK, 1 row affected (0.00 sec)mysql> SELECT * FROM book;
+------+------+-------+------------+------+
| id | name | price | publish | num |
+------+------+-------+------------+------+
| 3 | C++ | 13.5 | 2000-08-02 | 15 |
+------+------+-------+------------+------+
1 rows in set (0.00 sec)
PRIMARY KEY(主鍵約束)
NOT NULL
和 UNIQUE
的結合。確保某列(或兩個列多個列的結合)有唯一標識,有助于更容易更快速地找到表中的一個特定的記錄。
主鍵(primary key
)的特性即 非空且唯一,如果在沒有指定主鍵的時候,如果某一列(或一組列)具有非空且唯一的特性,他就會被暫定為主鍵,但是主鍵只能有一個。
表中的任何列都可以作為主鍵,只要它滿足以下條件:
- 任意兩行都不具有相同的主鍵值;
- 每個行都必須具有一個主鍵值(主鍵列不允許NULL值)。
上面提到多個列也可以作為主鍵。在使用多列作為主鍵時,上述條件必須應用到構成主鍵的所有列,所有列值的 組合 必須是唯一的(但單個列的值可以不唯一)。
除MySQL強制實施的規則外,應該堅持的幾個普遍認可的最好習慣為:
- 不更新主鍵列中的值;
- 不重用主鍵列的值;
- 不在主鍵列中使用可能會更改的值。(例如,如果使用一個名字作為主鍵以標識某個供應商,當該供應商合并和更改其名字時,必須更改這個主鍵。)
// 使用 NOT NULL UNIQUE
mysql> CREATE TABLE pen(-> id int NOT NULL UNIQUE,-> name varchar(12),-> price double,-> num int-> );
Query OK, 0 rows affected (0.06 sec)// id 的 KEY 屬性變為 PRI
mysql> DESC pen;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| num | int | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)// 使用 PRIMARY KEY
mysql> CREATE TABLE clothes(-> id int PRIMARY KEY,-> name varchar(12),-> price double,-> num int-> );
Query OK, 0 rows affected (0.02 sec)// id 的 KEY 屬性變為 PRI
mysql> DESC clothes;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| num | int | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
4 rows in set (0.01 sec)
但是 非空且唯一 并不一定就是主鍵,如果有多個非空不唯一,則只有第一個是主鍵。
// 將 id 和 num 都設置為非空且唯一
mysql> CREATE TABLE pen(-> id int NOT NULL UNIQUE,-> name varchar(12),-> price double,-> num int UNIQUE NOT NULL-> );
Query OK, 0 rows affected (0.02 sec)// 只有 id 是 PRI
mysql> DESC pen;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| num | int | NO | UNI | NULL | |
+-------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)
但我們之前也提到過,是允許 多個列 組成主鍵的,那么怎么實現呢?
mysql> CREATE TABLE pen(-> id int PRIMARY KEY,-> name varchar(12) PRIMARY KEY,-> price double,-> num int-> );
ERROR 1068 (42000): Multiple primary key defined
通過報錯我們發現,如果在兩個列后分別聲明主鍵的話會被認為 定義了多個主鍵。
// 正確做法是在聲明完所有列之后,再聲明主鍵是由哪些列構成的
mysql> CREATE TABLE pen(-> id int,-> name varchar(12),-> price double,-> num int,-> PRIMARY KEY(id, name)-> );
Query OK, 0 rows affected (0.03 sec)mysql> DESC pen;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(12) | NO | PRI | NULL | |
| price | double | YES | | NULL | |
| num | int | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
4 rows in set (0.01 sec)
AUTO_INCREMENT 自增
添加自增屬性的項必須為數字,并且必須為主鍵,并且只有缺省的時候才會使用自增。
- 如果插入的
AUTO_INCREMENT
屬性的列值為缺省,則賦值為上一條記錄的值+1
,沒有上一條記錄,則賦值為1
。 - 如果刪除了表中數據,序號并不會重置,而是繼續從刪除的位置自增。
mysql> CREATE TABLE pen(-> id int PRIMARY KEY AUTO_INCREMENT,-> name varchar(12),-> price double,-> num int-> );
Query OK, 0 rows affected (0.02 sec)// 可以看到 id 的 Extra 屬性變成了 auto_increment
mysql> DESC pen;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| num | int | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)// 加入一條未設 id 的數據
mysql> INSERT INTO pen(name, price, num) VALUES("A", 2.34, 5);
Query OK, 1 row affected (0.01 sec)// id 被自動設置為 1
mysql> SELECT * FROM pen;
+----+------+-------+------+
| id | name | price | num |
+----+------+-------+------+
| 1 | A | 2.34 | 5 |
+----+------+-------+------+
1 row in set (0.00 sec)// 增加兩條數據,一條設置 id
mysql> INSERT INTO pen VALUES(3, "B", 3.45, 7);
Query OK, 1 row affected (0.00 sec)
// 一條不設 id
mysql> INSERT INTO pen(name, price, num) VALUES("C", 6.34, 10);
Query OK, 1 row affected (0.00 sec)// 未設 id 的數據其 id 會變成上一條記錄的 id+1
mysql> SELECT * FROM pen;
+----+------+-------+------+
| id | name | price | num |
+----+------+-------+------+
| 1 | A | 2.34 | 5 |
| 3 | B | 3.45 | 7 |
| 4 | C | 6.34 | 10 |
+----+------+-------+------+
3 rows in set (0.00 sec)// 刪掉所有 id<=4 的數據
mysql> DELETE FROM pen WHERE id <= 4;
Query OK, 3 rows affected (0.00 sec)// 增加兩條數據,都沒有設置 id 值
mysql> INSERT INTO pen(name, price, num) VALUES("D", 7.34, 1);
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO pen(name, price, num) VALUES("E", 12.34, 13);
Query OK, 1 row affected (0.00 sec)// 新增的兩條數據的 id 并不是從 1 開始,而是根據上一條記錄(雖然它已經不存在了)的 id 自增
mysql> SELECT * FROM pen;
+----+------+-------+------+
| id | name | price | num |
+----+------+-------+------+
| 5 | D | 7.34 | 1 |
| 6 | E | 12.34 | 13 |
+----+------+-------+------+
2 rows in set (0.00 sec)
FOREIGN KEY(外鍵約束)
外鍵:外鍵為表中的某一列,包含了另一個表的主鍵,定義了兩個表之間的關系。例如學生表中存儲了班級的信息,但是在班級表中并沒有這個班級存在,就會導致數據出現沖突,所以必須將兩個表關聯起來。
語法
FOREIGN KEY (外鍵項) REFERENCES 關聯表名(關聯表中的對應項)
示例
// 創建班級表
mysql> CREATE TABLE class(-> id int PRIMARY KEY AUTO_INCREMENT-> );
Query OK, 0 rows affected (0.03 sec)
// 創建學生表
mysql> CREATE TABLE student(-> id int PRIMARY KEY AUTO_INCREMENT,-> num int,-> name varchar(10),-> classid int,-> FOREIGN KEY (classid) REFERENCES class(id)-> );
Query OK, 0 rows affected (0.03 sec)
// 創建兩個班級
mysql> INSERT INTO class VALUES(181);
Query OK, 1 row affected (0.01 sec)
mysql> INSERT INTO class VALUES(182);
Query OK, 1 row affected (0.00 sec)
// 插入三條學生數據
mysql> INSERT INTO student(name, classid) VALUES("李四", 182);
Query OK, 1 row affected (0.03 sec)
mysql> INSERT INTO student(name, classid) VALUES("陳六", 181);
Query OK, 1 row affected (0.00 sec)
// 第三條失敗,原因在于沒有186班
mysql> INSERT INTO student(name, classid) VALUES("張三", 186);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`student`, CONSTRAINT `student_ibfk_1` FOREIGN KEY (`classid`) REFERENCES `class` (`id`))
CHECK(檢查約束)
保證列中的值符合指定的條件。對于MySQL數據庫,對CHECK子句進行分析,但是忽略CHECK子句。
mysql> CREATE TABLE stu(-> id int,-> age int,-> name varchar(10),-> CHECK(id < 5)-> );
Query OK, 0 rows affected (0.03 sec)mysql> INSERT INTO stu(id) VALUES(3);
Query OK, 1 row affected (0.01 sec)mysql> INSERT INTO stu(id) VALUES(8);
ERROR 3819 (HY000): Check constraint 'stu_chk_1' is violated.
mysql> SELECT * FROM stu;
+------+------+------+
| id | age | name |
+------+------+------+
| 3 | NULL | NULL |
+------+------+------+
1 row in set (0.00 sec)
值得一提的是,在 MySQL 8.0.16
版本之前的 CHECK 約束
,能被解析但是被忽略掉了,就是 不符合CHECK的數據依然可以被加入到表中。 而在之后的版本上,支持 CHECK約束
,但仍有缺陷!想要 INSER INTO
一個不滿足 CHECK
的數據時確實不能通過(詳見上面的示例中),但是如果 INSERT INTO
時,未設置被 CHECK
約束的 列
,那么 即使這個列的默認值不符合 CHECK約束
,也還是可以創建成功。
可以看到 主動賦值 不滿足 CHECK
的語句會報錯,而 默認值 不滿足 CHECK
的語句卻可以成功插入。
表的設計
表的關系
一對一
例如人和身份證的關系,每個人都對應有著只屬于自己的身份證:
一對多
例如學生和班級的關系,一個班級擁有多個學生,但是一個學生只能屬于一個班級:
多對多
例如學生、課程、選課表的關系。一個學生可以選擇多門課程,一個課程也可以被多個學生選擇:
三大范式
表的關系只是設計的最基礎的一項,考慮好關系,確認好數據項,將數據填進去即可。
但是那樣的設計并不合理,可能會存在 數據冗余、傳輸性能、查詢性能 等問題,所以需要用到三大范式來規范數據庫表的設計,減少數據庫的冗余性。
范式是針對數據庫表設計的幾種方案,目前關系數據庫有六種范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又稱完美范式)。
通常我們使用的都是第一范式(1NF)、第二范式(2NF)、第三范式(3NF),所以又將他們稱為三大范式。
第一范式
要求數據庫表的每一列都是不可分割的原子數據項。
在這個表中,家庭信息和學校信息并不是原子的,例如家庭信息中包含了家庭組成和所在地,學校信息包含了年級和學位。
對于第一范式,需要確保每一項數據都是不可分割的原子性數據,不能是一個集合。因此要做出如下調整:
第二范式
在第一范式的基礎上,非主鍵數據必須完全依賴主鍵,不能部分依賴(針對組合主鍵)。
拿下面這個表來說,相同 訂單號 的 訂單時間和訂單金額 是 一樣 的,但是相同 產品號 的 訂單時間和訂單金額 卻 不一樣 。這說明訂單時間和訂單金額 部分依賴主鍵 —— 只依賴了組合主鍵中的訂單號而沒有依賴產品號 。
所以需要將其分割出去單獨建立一個表:
第三范式
在第二范式的基礎上,每一個非主鍵數據都必須要和主鍵直接依賴而非間接依賴,即不能依賴非主鍵數據。旨在體現依賴關系不可傳遞性。
如下圖,班主任性別 與 班主任年齡 直接依賴于班主任姓名(非主鍵數據),而與 主鍵——學號 并沒有直接的依賴關系,而是間接,這就是依賴傳遞:
所以需要將這兩項分割出去單獨建表: