1. 程式人生 > 實用技巧 >(3.4)表相關操作之完整性約束

(3.4)表相關操作之完整性約束

一、介紹


約束條件與資料型別的寬度一樣,都是可選引數

作用:用於保證資料的完整性和一致性
主要分為:

PRIMARY KEY (PK)    標識該欄位為該表的主鍵,可以唯一的標識記錄
FOREIGN KEY (FK)    標識該欄位為該表的外來鍵
NOT NULL    標識該欄位不能為空
UNIQUE KEY (UK)    標識該欄位的值是唯一的
AUTO_INCREMENT    標識該欄位的值自動增長(整數型別,而且為主鍵)
DEFAULT    為該欄位設定預設值

UNSIGNED 無符號
ZEROFILL 使用0填充

說明:

1,是否允許為空,預設NULL,可設定NOT NULL
,欄位不允許為空,必須賦值 2,欄位是否有預設值,預設的預設值是NULL,如果插入記錄時不給欄位賦值,此欄位使用預設值 sex enum('male','female') not null default 'male' age int unsigned NOT NULL default 20 必須為正值(無符號) 不允許為空 預設是20 3,是否是key 主鍵 primary key 外來鍵 foreign key 索引 (index,unique...)

二、not null 與 default


是否可空,null表示空,非字串

not null - 不可空
null - 可空

預設值,建立列時可以指定預設值,當插入資料時如果未主動設定,則自動新增預設值

create table tb1(
nid int not null defalut 2,
num int not null
)

驗證:

1)not null

==================not null====================
mysql> create table t1(id int);   # id欄位預設可以插入空
mysql> desc t1;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | +-------+---------+------+-----+---------+-------+ mysql> insert into t1 values();   # 可以插入空 mysql> create table t2(id int not null);   # 設定欄位id不為空 mysql> desc t2; +-------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+---------+------+-----+---------+-------+ | id | int(11) | NO | | NULL | | +-------+---------+------+-----+---------+-------+ mysql> insert into t2 values();     # 不能插入空 ERROR 1364 (HY000): Field 'id' doesn't have a default value

2)default

==================default====================
#設定id欄位有預設值後,則無論id欄位是null還是not null,都可以插入空,插入空預設填入default指定的預設值
mysql> create table t3(id int default 1);    # 插入空值,會變成預設值 1
mysql> alter table t3 modify id int not null default 1;  # 改成不為空,但如果插入空值,還是會變成預設值 1



==================綜合練習====================
mysql> create table student(
    -> name varchar(20) not null,
    -> age int(3) unsigned not null default 18,
    -> sex enum('male','female') default 'male',
    -> hobby set('play','study','read','music') default 'play,music'
    -> );
mysql> desc student;
+-------+------------------------------------+------+-----+------------+-------+
| Field | Type                               | Null | Key | Default    | Extra |
+-------+------------------------------------+------+-----+------------+-------+
| name  | varchar(20)                        | NO   |     | NULL       |       |
| age   | int(3) unsigned                    | NO   |     | 18         |       |
| sex   | enum('male','female')              | YES  |     | male       |       |
| hobby | set('play','study','read','music') | YES  |     | play,music |       |
+-------+------------------------------------+------+-----+------------+-------+
mysql> insert into student(name) values('zixi');
mysql> select * from student;
+------+-----+------+------------+
| name | age | sex  | hobby      |
+------+-----+------+------------+
| zixi |  18 | male | play,music |
+------+-----+------+------------+

三、unique


設定唯一約束:

1)單列唯一(單列主鍵)

# 單列唯一,單列主鍵
============
設定唯一約束 UNIQUE=============== 方法一: create table department1(        # 存放部門資訊 id int, name char(20) unique,      # 部門名不能重複,設定唯一 comment char(100) ); 方法二: create table department2( id int, name varchar(20), comment varchar(100), unique(name)      # 單列唯一 ); mysql> insert into department1 values(1,'IT','技術'); Query OK, 1 row affected (0.00 sec) mysql> insert into department1 values(1,'IT','技術'); ERROR 1062 (23000): Duplicate entry 'IT' for key 'name'

not null + unique 的化學反應:

mysql> create table t1(id int not null unique);
Query OK, 0 rows affected (0.02 sec)

mysql> desc t1;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | NO   | PRI | NULL    |       |
+-------+---------+------+-----+---------+-------+
1 row in set (0.00 sec)

2)聯合唯一(多列唯一):

mysql> create table service(
    -> id int,
    -> name char(20),
    -> ip char(15) not null,
    -> port int not null,
    -> unique(ip,port)      # 聯合唯一
    -> );
Query OK, 0 rows affected (0.30 sec)

mysql> desc service;
+-------+----------+------+-----+---------+-------+
| Field | Type     | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| id    | int      | YES  |     | NULL    |       |
| name  | char(20) | YES  |     | NULL    |       |
| ip    | char(15) | NO   | PRI | NULL    |       |
| port  | int      | NO   | PRI | NULL    |       |
+-------+----------+------+-----+---------+-------+
4 rows in set (0.00 sec)

mysql> insert into service values
    -> (1,'nginx','192.168.0.10',80),
    -> (2,'haproxy','192.168.0.20',80),
    -> (3,'mysql','192.168.0.30',3306);
Query OK, 3 rows affected (0.07 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> insert into service(name,host,port) values('nginx','192.168.0.10',80);
ERROR 1054 (42S22): Unknown column 'host' in 'field list'
mysql>

四、primary key


從約束角度看 primary key欄位的值不為空且唯一,那我們直接使用 not null + unique不就可以了嗎,要它幹什麼?

主鍵 primary key是 innodb儲存引擎組織資料的依據,innodb稱之為索引組織表,一張表中必須有且只有一個主鍵,主鍵可以幫你組織表的資料,可以提高查詢速度。

一個表中可以:

1,單列做主鍵:

============單列做主鍵===============
# 方法一:not null+unique
create table department1(
id int not null unique,     # 主鍵
name varchar(20) not null unique,
comment varchar(100)
);

mysql> desc department1;
+---------+--------------+------+-----+---------+-------+
| Field   | Type         | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| id      | int(11)      | NO   | PRI | NULL    |       |
| name    | varchar(20)  | NO   | UNI | NULL    |       |
| comment | varchar(100) | YES  |     | NULL    |       |
+---------+--------------+------+-----+---------+-------+
rows in set (0.01 sec)

# 方法二:在某一個欄位後用 primary key
create table department2(
id int primary key,   # 主鍵
name varchar(20),
comment varchar(100)
);

mysql> desc department2;
+---------+--------------+------+-----+---------+-------+
| Field   | Type         | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| id      | int(11)      | NO   | PRI | NULL    |       |
| name    | varchar(20)  | YES  |     | NULL    |       |
| comment | varchar(100) | YES  |     | NULL    |       |
+---------+--------------+------+-----+---------+-------+
rows in set (0.00 sec)

# 方法三:在所有欄位後單獨定義 primary key
create table department3(
id int,
name varchar(20),
comment varchar(100),
constraint pk_name primary key(id);   # 建立主鍵併為其命名pk_name

mysql> desc department3;
+---------+--------------+------+-----+---------+-------+
| Field   | Type         | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| id      | int(11)      | NO   | PRI | NULL    |       |
| name    | varchar(20)  | YES  |     | NULL    |       |
| comment | varchar(100) | YES  |     | NULL    |       |
+---------+--------------+------+-----+---------+-------+
rows in set (0.01 sec)

2,複合主鍵(多列做主鍵)

==================多列做主鍵================
create table service(
ip varchar(15),
port char(5),
service_name varchar(10) not null,
primary key(ip,port)
);


mysql> desc service;
+--------------+-------------+------+-----+---------+-------+
| Field        | Type        | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+-------+
| ip           | varchar(15) | NO   | PRI | NULL    |       |
| port         | char(5)     | NO   | PRI | NULL    |       |
| service_name | varchar(10) | NO   |     | NULL    |       |
+--------------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> insert into service values
    -> ('172.16.45.10','3306','mysqld'),
    -> ('172.16.45.11','3306','mariadb')
    -> ;
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> insert into service values ('172.16.45.10','3306','nginx');
ERROR 1062 (23000): Duplicate entry '172.16.45.10-3306' for key 'PRIMARY'

五、auto_increment


約束欄位為自動增長,被約束的欄位必須同時被 key約束。

# 不指定id,則自動增長
create table student(
id int primary key auto_increment,
name varchar(20),
sex enum('male','female') default 'male'
);

mysql> desc student;
+-------+-----------------------+------+-----+---------+----------------+
| Field | Type                  | Null | Key | Default | Extra          |
+-------+-----------------------+------+-----+---------+----------------+
| id    | int(11)               | NO   | PRI | NULL    | auto_increment |
| name  | varchar(20)           | YES  |     | NULL    |                |
| sex   | enum('male','female') | YES  |     | male    |                |
+-------+-----------------------+------+-----+---------+----------------+
mysql> insert into student(name) values
    -> ('zixi'),
    -> ('suos')
    -> ;

mysql> select * from student;
+----+------+------+
| id | name | sex  |
+----+------+------+
|  1 | zixi | male |
|  2 | suos | male |
+----+------+------+


# 也可以指定id
mysql> insert into student values(4,'asb','female');
Query OK, 1 row affected (0.00 sec)

mysql> insert into student values(7,'bsb','female');
Query OK, 1 row affected (0.00 sec)

mysql> select * from student;
+----+------+--------+
| id | name | sex    |
+----+------+--------+
|  1 | zixi | male   |
|  2 | suos | male   |
|  4 | asb  | female |
|  7 | bsb  | female |
+----+------+--------+


# 對於自增的欄位,在用  delete刪除後,再插入值,該欄位仍按照刪除前的位置繼續增長,delete 經常與 where連用。
mysql> delete from student;
Query OK, 4 rows affected (0.00 sec)

mysql> select * from student;
Empty set (0.00 sec)

mysql> insert into student(name) values('ysb');
mysql> select * from student;
+----+------+------+
| id | name | sex  |
+----+------+------+
|  8 | ysb  | male |
+----+------+------+

# 應該用 truncate清空表,比起 delete一條一條地刪除記錄,truncate是直接清空表,在刪除大表時用它
mysql> truncate student;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into student(name) values('zixi');
Query OK, 1 row affected (0.01 sec)

mysql> select * from student;
+----+------+------+
| id | name | sex  |
+----+------+------+
|  1 | zixi | male |
+----+------+------+
1 row in set (0.00 sec)

步長:auto_increment_increment,起始偏移量:auto_increment_offset (瞭解)

# 在建立完表後,修改自增欄位的起始值
mysql> create table student(
    -> id int primary key auto_increment,
    -> name varchar(20),
    -> sex enum('male','female') default 'male'
    -> );

mysql> alter table student auto_increment=3;

mysql> show create table student;
.......
ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

mysql> insert into student(name) values('egon');
Query OK, 1 row affected (0.01 sec)

mysql> select * from student;
+----+------+------+
| id | name | sex  |
+----+------+------+
|  3 | egon | male |
+----+------+------+
row in set (0.00 sec)

mysql> show create table student;
.......
ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8


# 也可以建立表時指定auto_increment的初始值,注意初始值的設定為表選項,應該放到括號外
create table student(
id int primary key auto_increment,
name varchar(20),
sex enum('male','female') default 'male'
)auto_increment=3;




# 設定步長
sqlserver自增步長:
    # 基於表級別
    create table t1(
        id int。。。
    )engine=innodb,auto_increment=2 步長=2 default charset=utf8

mysql自增的步長:
    show session variables like 'auto_inc%';
    
    # 基於會話級別
    set session auth_increment_increment=2   # 修改會話級別的步長

    # 基於全域性級別的
    set global auth_increment_increment=2    # 修改全域性級別的步長(所有會話都生效)


# !!!注意了注意了注意了!!!
If the value of auto_increment_offset is greater than that of auto_increment_increment, the value of auto_increment_offset is ignored. 
翻譯:如果auto_increment_offset的值大於auto_increment_increment的值,則auto_increment_offset的值會被忽略 ,這相當於第一步步子就邁大了,扯著了蛋
比如:設定auto_increment_offset=3,auto_increment_increment=2




mysql> set session auto_increment_increment=5;    # session 本次會話
Query OK, 0 rows affected (0.00 sec)

mysql> set global auto_increment_offset=3;       # global 全域性,需要 exit退出,重新載入一邊,才好用
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'auto_in%';   # 查變數, % 是任意字元的所有變數
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| auto_increment_increment | 1     |
| auto_increment_offset    | 1     |
+--------------------------+-------+



create table student(
id int primary key auto_increment,
name varchar(20),
sex enum('male','female') default 'male'
);

mysql> insert into student(name) values('zixi1'),('zixi2'),('zixi3');
mysql> select * from student;
+----+-------+------+
| id | name  | sex  |
+----+-------+------+
|  3 | zixi1 | male |
|  8 | zixi2 | male |
| 13 | zixi3 | male |
+----+-------+------+

六、foreign key(外來鍵)


1,快速理解 foreign key:

建立表之間的關係

員工資訊表有三個欄位:工號 姓名 部門

公司有3個部門,但是有 1個億的員工,那意味著部門這個欄位需要重複儲存,部門名字越長,越浪費

解決方法:

我們完全可以定義一個部門表

然後讓員工資訊表關聯該表,如何關聯,即 foreign key

最好是從邏輯意義上實現兩張表之間的關係,從應用程式中控制,不要在資料庫中把表建上硬性關係,把兩張表耦合到一起了,寫專案的時候,首先是設計表和結構,把表之間的關聯關係做好,一旦用 外來鍵做這種硬性的關係,意味著所有的表被你強耦合到一起了,一旦以後涉及到擴充套件,會非常麻煩。

示範:

# 1,建立表關係
# 表型別必須是 innodb儲存引擎,且被關聯的欄位,即 references指定的另外一個表的欄位,必須保證唯一
create table department( id int primary key, name varchar(20) not null )engine=innodb; # dpt_id外來鍵,關聯父表(department主鍵id),同步更新,同步刪除 create table employee( id int primary key, name varchar(20) not null, dpt_id int, constraint fk_name foreign key(dpt_id) references department(id) on delete cascade    # 刪除同步 on update cascade     # 更新同步 )engine=innodb;
# 2,往表中插入資料 # 先往父表 department中插入記錄
insert into department values (1,'艾歐尼亞'), (2,'德瑪西亞'), (3,'無畏先鋒'); # 再往子表 employee中插入記錄 insert into employee values (1,'suos',1), (2,'zixi1',2), (3,'zixi2',2), (4,'zixi3',2), (5,'李坦克',3), (6,'劉飛機',3), (7,'張火箭',3), (8,'林子彈',3), (9,'加特林',3) ; # 刪父表 department,子表 employee中對應的記錄跟著刪 mysql> delete from department where id=3; mysql> select * from employee; +----+-------+--------+ | id | name | dpt_id | +----+-------+--------+ | 1 | suos | 1 | | 2 | zixi1 | 2 | | 3 | zixi2 | 2 | | 4 | zixi3 | 2 | +----+-------+--------+ # 更新父表 department,子表 employee中對應的記錄跟著改 mysql> update department set id=22222 where id=2; mysql> select * from employee; +----+-------+--------+ | id | name | dpt_id | +----+-------+--------+ | 1 | suos | 1 | | 3 | zixi2 | 22222 | | 4 | zixi3 | 22222 | | 5 | zixi1 | 22222 | +----+-------+--------+

2,如何找出兩張表之間的關係

分析步驟:
# 1、先站在左表的角度去找
是否左表的多條記錄可以對應右表的一條記錄,如果是,則證明左表的一個欄位foreign key 右表一個欄位(通常是id)
# 多個員工對應一個部門,而多個部門不能對應一個員工(大公司)
# 2、再站在右表的角度去找 是否右表的多條記錄可以對應左表的一條記錄,如果是,則證明右表的一個欄位foreign key 左表一個欄位(通常是id) # 3、總結: # 多對一: 如果只有步驟1成立,則是左表多對一右表 如果只有步驟2成立,則是右表多對一左表 # 多對多 如果步驟1和2同時成立,則證明這兩張表是一個雙向的多對一,即多對多,需要定義一個這兩張表的關係表來專門存放二者的關係 # 一對一: 如果1和2都不成立,而是左表的一條記錄唯一對應右表的一條記錄,反之亦然。這種情況很簡單,就是在左表foreign key右表的基礎上,將左表的外來鍵欄位設定成unique即可

3,建立表之間的關係

1)多對一

# 一對多或稱為多對一
三張表:出版社,作者資訊,書

一對多(或多對一):一個出版社可以出版多本書

關聯方式:foreign key
===================== 多對一 =====================

# 出版社 create table press( id int primary key auto_increment, name varchar(20) );
# 書
create table book( id int primary key auto_increment, name varchar(20), press_id int not null, foreign key(press_id) references press(id) on delete cascade on update cascade ); insert into press(name) values ('北京工業地雷出版社'), ('人民音樂不好聽出版社'), ('智慧財產權沒有用出版社') ; insert into book(name,press_id) values ('九陽神功',1), ('九陰真經',2), ('九陰白骨爪',2), ('獨孤九劍',3), ('降龍十巴掌',2), ('葵花寶典',3) ;

其他例子:

一夫多妻制

# 妻子表的丈夫id外來鍵到丈夫表的id

2)多對多

# 多對多
三張表:出版社,作者資訊,書

多對多:一個作者可以寫多本書,一本書也可以有多個作者,雙向的一對多,即多對多
  
關聯方式:foreign key+一張新的表
=====================多對多=====================
create table author(
id int primary key auto_increment,
name varchar(20)
);


# 這張表就存放作者表與書表的關係,即查詢二者的關係查這表就可以了
create table author2book(
id int not null unique auto_increment,
author_id int not null,
book_id int not null,
constraint fk_author foreign key(author_id) references author(id)
on delete cascade
on update cascade,
constraint fk_book foreign key(book_id) references book(id)
on delete cascade
on update cascade,
primary key(author_id,book_id)
);


# 插入四個作者,id依次排開
insert into author(name) values('zixi'),('suos'),('jiej'),('ruiw');

# 每個作者與自己的代表作如下
1 zixi: 
      1 九陽神功
      2 九陰真經
      3 九陰白骨爪
      4 獨孤九劍
      5 降龍十巴掌
      6 葵花寶典


2 suos: 
      1 九陽神功
      6 葵花寶典

3 jiej:
      4 獨孤九劍
      5 降龍十巴掌
      6 葵花寶典

4 ruiw:
      1 九陽神功


insert into author2book(author_id,book_id) values
(1,1),
(1,2),
(1,3),
(1,4),
(1,5),
(1,6),
(2,1),
(2,6),
(3,4),
(3,5),
(3,6),
(4,1)
;

其他例子:

# 單張表:使用者表+相親關係表,相當於:使用者表+相親關係表+使用者表
# 多張表:使用者表+使用者與主機關係表+主機表

# 中間那一張存放關係的表,對外關聯的欄位可以聯合唯一

3)一對一

# 一對一
兩張表:學生表和客戶表

一對一:一個學生是一個客戶,一個客戶有可能變成一個學校,即一對一的關係

關聯方式:foreign key + unique
# 一定是 student來 foreign key表 customer,這樣就保證了:
# 1 學生一定是一個客戶,
# 2 客戶不一定是學生,但有可能成為一個學生


create table customer(
id int primary key auto_increment,
name varchar(20) not null,
qq varchar(10) not null,
phone char(16) not null
);


create table student(
id int primary key auto_increment,
class_name varchar(20) not null,
customer_id int unique,       # 該欄位一定要是唯一的
foreign key(customer_id) references customer(id)     # 外來鍵的欄位一定要保證unique
on delete cascade
on update cascade
);


# 增加客戶
insert into customer(name,qq,phone) values
('李飛機','31811231',13811341220),
('王大炮','123123123',15213146809),
('守榴彈','283818181',1867141331),
('吳坦克','283818181',1851143312),
('贏火箭','888818181',1861243314),
('戰地雷','112312312',18811431230)
;


# 增加學生
insert into student(class_name,customer_id) values
('脫產3班',3),
('週末19期',4),
('週末19期',5)
;

其他例子:

例一:一個使用者只有一個部落格

    使用者表:
    id  name
    1    zixi
    2    suos
    3    jiej


    部落格表   
           fk+unique
    id url name_id
    1  xxxx   1
    2  yyyy   3
    3  zzz    2



例二:一個管理員唯一對應一個使用者
    使用者表:
    id user  password
    1  zixi    xxxx
    2  suos    yyyy

    管理員表:
       fk+unique
    id user_id password
    1   1      xxxxx
    2   2      yyyyy

七、作業


練習:賬號資訊表,使用者組,主機表,主機組

#使用者表
create table user(
id int not null unique auto_increment,
username varchar(20) not null,
password varchar(50) not null,
primary key(username,password)
);

insert into user(username,password) values
('root','123'),
('egon','456'),
('alex','alex3714')
;


#使用者組表
create table usergroup(
id int primary key auto_increment,
groupname varchar(20) not null unique
);

insert into usergroup(groupname) values
('IT'),
('Sale'),
('Finance'),
('boss')
;


#主機表
create table host(
id int primary key auto_increment,
ip char(15) not null unique default '127.0.0.1'
);

insert into host(ip) values
('172.16.45.2'),
('172.16.31.10'),
('172.16.45.3'),
('172.16.31.11'),
('172.10.45.3'),
('172.10.45.4'),
('172.10.45.5'),
('192.168.1.20'),
('192.168.1.21'),
('192.168.1.22'),
('192.168.2.23'),
('192.168.2.223'),
('192.168.2.24'),
('192.168.3.22'),
('192.168.3.23'),
('192.168.3.24')
;


#業務線表
create table business(
id int primary key auto_increment,
business varchar(20) not null unique
);
insert into business(business) values
('輕鬆貸'),
('隨便花'),
('大富翁'),
('窮一生')
;


#建關係:user與usergroup

create table user2usergroup(
id int not null unique auto_increment,
user_id int not null,
group_id int not null,
primary key(user_id,group_id),
foreign key(user_id) references user(id),
foreign key(group_id) references usergroup(id)
);

insert into user2usergroup(user_id,group_id) values
(1,1),
(1,2),
(1,3),
(1,4),
(2,3),
(2,4),
(3,4)
;



#建關係:host與business

create table host2business(
id int not null unique auto_increment,
host_id int not null,
business_id int not null,
primary key(host_id,business_id),
foreign key(host_id) references host(id),
foreign key(business_id) references business(id)
);

insert into host2business(host_id,business_id) values
(1,1),
(1,2),
(1,3),
(2,2),
(2,3),
(3,4)
;

#建關係:user與host

create table user2host(
id int not null unique auto_increment,
user_id int not null,
host_id int not null,
primary key(user_id,host_id),
foreign key(user_id) references user(id),
foreign key(host_id) references host(id)
);

insert into user2host(user_id,host_id) values
(1,1),
(1,2),
(1,3),
(1,4),
(1,5),
(1,6),
(1,7),
(1,8),
(1,9),
(1,10),
(1,11),
(1,12),
(1,13),
(1,14),
(1,15),
(1,16),
(2,2),
(2,3),
(2,4),
(2,5),
(3,10),
(3,11),
(3,12)
;
result

作業: