# MySQL-2
## 高級數據操作
### 增 - 主鍵衝突(Duplicate key)
> #### 基本插入數據語法
> `insert into tableName [(字段列表)] values (值列表)`
> - 主鍵衝突: 插入數據時, 對應的主鍵已經存在
```sql
mysql> -- 表內有一筆數據, ID 為主鍵, 且自增長
mysql> select * from my_pri;
+----+-------+
| id | name |
+----+-------+
| 1 | GODJJ |
+----+-------+
1 row in set (0.00 sec)
mysql> show create table my_pri;
...
| my_pri | CREATE TABLE `my_pri` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 |
...
mysql> -- 當我插入重複主鍵的數據時, 當然會失敗
mysql> insert into my_pri values (1, 'GEAR');
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
```
> - 解決辦法1. 更新
> `insert into tableName [(字段列表)] values (值列表) on duplicate key update 字段 = newValue`
> - 會有兩行受影響: insert 與 update
```sql
mysql> insert into my_pri values (1, 'GEAR') on duplicate key update name = 'GEAR';
Query OK, 2 rows affected (0.00 sec) -- 兩行受影響
mysql> select * from my_pri;
+----+------+
| id | name |
+----+------+
| 1 | GEAR |
+----+------+
1 row in set (0.00 sec)
```
> - 解決辦法2. 替換
> `replace into tableName [(字段列表: 含主鍵)] values (值列表)`
> - 兩行受影響, 因為先刪除數據再新增數據
```sql
mysql> select * from my_pri;
+----+------+
| id | name |
+----+------+
| 1 | GEAR |
| 2 | BEBE | -- 有一筆 2
+----+------+
2 rows in set (0.00 sec)
mysql> replace into my_pri values (2, 'HAHA'); -- 更新成功
Query OK, 2 rows affected (0.01 sec) -- 兩行受影響
mysql> select * from my_pri;
+----+------+
| id | name |
+----+------+
| 1 | GEAR |
| 2 | HAHA | -- 成果
+----+------+
2 rows in set (0.00 sec)
```
> - 兩個語句都是在衝突時才會影響兩行
```sql
mysql> -- 當沒有衝突時, 都只執行插入
mysql> insert into my_pri values (null, 'HOHO') on duplicate key update name = 'HOHO';
Query OK, 1 row affected (0.01 sec)
mysql> replace into my_pri values (null, 'WOWO');
Query OK, 1 row affected (0.00 sec)
```
### 增-蠕蟲複製
> - 有時想要測試正在線上跑的數據, 但又怕影響到線上的服務器, 此時就可以考慮使用蠕蟲複製
> - 從已有數據的表中去獲取數據, 然後再自我複製, 已達到數據倍增的效果
> - 可以迅速讓表中數據快速膨脹到想要達到的量級, 以利測試壓力及效率
> #### 複製表結構
> `create table tableName like databaseName.tableName`
```sql
mysql> desc my_test;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| name | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
1 row in set (0.00 sec)
mysql> -- 複製表結構
mysql> create table my_copy like my_test;
Query OK, 0 rows affected (0.01 sec)
mysql> desc my_copy;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| name | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
1 row in set (0.00 sec)
```
> #### 複製數據
> `insert into tableName [(字段列表)] select 字段列表或* from tableName`
> - 先查出數據後, 將數據新增到指定的表中
> - 所謂蠕蟲複製就是重複複製自己的數據
> - 2->4->8->16
> - 通常使用場景就是先把現有數據拿下來,然後迅速膨脹幾倍看未來會有什麼問題
```sql
mysql> select * from my_copy;
Empty set (0.00 sec) -- 沒有數據
mysql> select * from my_test;
+------+
| name |
+------+
| haha |
| wowo |
| GOGO |
| BUBU |
+------+
4 rows in set (0.00 sec)
mysql> -- 複製數據
mysql> insert into my_copy select * from my_test;
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> select * from my_copy;
+------+
| name |
+------+
| haha |
| wowo |
| GOGO |
| BUBU |
+------+
4 rows in set (0.00 sec)
mysql> -- 蠕蟲複製
mysql> insert into my_copy select * from my_copy;
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> insert into my_copy select * from my_copy;
Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> select * from my_copy;
+------+
| name |
+------+
| haha |
| wowo |
| GOGO |
| BUBU | -- 複製前 4 筆
| haha |
| wowo |
| GOGO |
| BUBU | -- 複製第一次 8 筆
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU | -- 複製第二次 16 筆
+------+
16 rows in set (0.00 sec)
```
### 改 - 限制條件
> #### 基本更新數據語法
> `update tableName set 字段 = 值 [where 條件]`
> #### 更新數據限制條件
> `update tableName set 字段 = 值 [where 條件] [limit 限制數量]`
```sql
mysql> select * from my_copy;
+------+
| name |
+------+
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
+------+
16 rows in set (0.00 sec)
mysql> -- 把前面 2 個 name='haha' 改成 'GGGG'
mysql> update my_copy set name = 'GGGG' where name = 'haha' limit 2;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0
mysql> select * from my_copy;
+------+
| name |
+------+
| GGGG | -- 更新
| wowo |
| GOGO |
| BUBU |
| GGGG | -- 更新
| wowo |
| GOGO |
| BUBU |
| haha | -- 沒更新~
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
+------+
16 rows in set (0.00 sec)
```
### 刪除數據
> #### 基本刪除數據語法
> `delete from tableName [where 條件]`
> #### 刪除數據條件限制
> `delete from tableName [where 條件] [limit 數量]`
```sql
mysql> select * from my_copy;
+------+
| name |
+------+
| GGGG |
| wowo |
| GOGO |
| BUBU |
| GGGG |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
+------+
16 rows in set (0.00 sec)
mysql> -- 資料砍半
mysql> delete from my_copy limit 8;
Query OK, 8 rows affected (0.00 sec)
mysql> select * from my_copy;
+------+
| name |
+------+
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
+------+
8 rows in set (0.00 sec)
```
### truncate
`truncate tableName`
> - 數據刪除不會改變表結構, 例如自增長是幾就是幾, 不會因為數據刪掉而改變
> - 想要重置表結構, 只能刪除表再重建表
> - truncate 就是幹這件事
> - Q. truncate 如何知道新增的 SQL 語句?
> - 任何表的創建語句都會保存一個表的結構文件
> - truncate 刪掉表之後會去那裡查語句來重建
> - 說白了他就是執行 drop table xx 跟 create table xx 兩句
```sql
mysql> select * from my_pri;
+----+------+
| id | name |
+----+------+
| 1 | GEAR |
| 2 | HAHA |
| 3 | HOHO |
| 4 | WOWO |
+----+------+
4 rows in set (0.00 sec)
mysql> show create table my_pri;
...
| Table | Create Table
| my_pri | CREATE TABLE `my_pri` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 | -- 自增長為 5
...
1 row in set (0.00 sec)
mysql> -- 刪除所有數據
mysql> delete from my_pri;
Query OK, 4 rows affected (0.00 sec)
mysql> show create table my_pri;
| Table | Create Table
| my_pri | CREATE TABLE `my_pri` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 | -- 自增長還是 5
mysql> -- 清空表,重置自增長
mysql> truncate my_pri;
Query OK, 0 rows affected (0.02 sec)
mysql> show create table my_pri;
| Table | Create Table
| my_pri | CREATE TABLE `my_pri` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 | -- 搞定
```
## 查詢數據
> - 基本語法
> `select 字段列表/* from tableName [where 條件]`
> - 完整語法
> `select [select 選項] 字段列表[字段別名]/* from 數據源 [where 條件] [group by 子句] [having 子句] [limit 子句]`
### select 選項
> - 對查出結果的處理方式
> - all: 保留所有結果, 默認
> - distinct: 去重複(所有字段都相同才去)
```sql
mysql> select * from my_copy;
+------+
| name |
+------+
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
+------+
16 rows in set (0.00 sec)
mysql> select all * from my_copy;
+------+
| name |
+------+
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
| haha |
| wowo |
| GOGO |
| BUBU |
+------+
16 rows in set (0.00 sec)
mysql> select distinct * from my_copy;
+------+
| name |
+------+
| haha |
| wowo |
| GOGO |
| BUBU |
+------+
4 rows in set (0.01 sec)
```
### 字段別名
`字段名 [as] 別名`
> - 對查出的結果字段重命名
```sql
mysql> select * from my_oname;
+----+-----------+-------+
| id | num | name |
+----+-----------+-------+
| 1 | NUM: 0001 | GODJJ |
| 2 | NUM: 0002 | GEAR |
| 3 | NUM: 0003 | BEBE |
+----+-----------+-------+
3 rows in set (0.00 sec)
mysql> -- 重命名
mysql> select
id as '自增長主鍵',
num as '編號',
name '名字' -- 沒 as 也可以
from my_oname;
+-----------------+-----------+--------+
| 自增長主鍵 | 編號 | 名字 |
+-----------------+-----------+--------+
| 1 | NUM: 0001 | GODJJ |
| 2 | NUM: 0002 | GEAR |
| 3 | NUM: 0003 | BEBE |
+-----------------+-----------+--------+
3 rows in set (0.00 sec)
```
### 數據源
> - 數據的來源,
> - 關係型數據庫的數據源都是數據表: 只要是類似二維表, 都能為數據源
> - 數據源分三種
> - 單表數據源
> - 多表數據源
> - 查詢語句(子查詢)
> #### 單表數據源
```sql
mysql> select * from my_oname; -- 來源只有一張表
+----+-----------+-------+
| id | num | name |
+----+-----------+-------+
| 1 | NUM: 0001 | GODJJ |
| 2 | NUM: 0002 | GEAR |
| 3 | NUM: 0003 | BEBE |
+----+-----------+-------+
3 rows in set (0.00 sec)
```
> #### 多表數據源
> `select * from tableName1, tableName2, ...`
```sql
mysql> select * from my_test; -- 單表數據源
+------+
| name |
+------+
| haha |
| wowo |
| GOGO |
| BUBU |
+------+
4 rows in set (0.00 sec)
mysql> select * from my_oname; -- 單表數據源
+----+-----------+-------+
| id | num | name |
+----+-----------+-------+
| 1 | NUM: 0001 | GODJJ |
| 2 | NUM: 0002 | GEAR |
| 3 | NUM: 0003 | BEBE |
+----+-----------+-------+
3 rows in set (0.00 sec)
mysql> select * from my_oname, my_test; -- 多表數據源
+----+-----------+-------+------+
| id | num | name | name |
+----+-----------+-------+------+
| 1 | NUM: 0001 | GODJJ | haha |
| 2 | NUM: 0002 | GEAR | haha |
| 3 | NUM: 0003 | BEBE | haha |
| 1 | NUM: 0001 | GODJJ | wowo |
| 2 | NUM: 0002 | GEAR | wowo |
| 3 | NUM: 0003 | BEBE | wowo |
| 1 | NUM: 0001 | GODJJ | GOGO |
| 2 | NUM: 0002 | GEAR | GOGO |
| 3 | NUM: 0003 | BEBE | GOGO |
| 1 | NUM: 0001 | GODJJ | BUBU |
| 2 | NUM: 0002 | GEAR | BUBU |
| 3 | NUM: 0003 | BEBE | BUBU |
+----+-----------+-------+------+
12 rows in set (0.00 sec)
```
> - 顯示的結果是從 A 表一條數據去匹配 B 表的所有數據, 而且全部保留(數據與字段)
> 這種結果在數學上稱為 [Cartesian product](https://zh.wikipedia.org/wiki/%E7%AC%9B%E5%8D%A1%E5%84%BF%E7%A7%AF)
> - 基本上 Cartesian product 沒啥屁用又佔空間,必須盡量避免
### 子查詢
`select * from (select * from tableName) as 別名`
> - 數據來源是一條查詢語句的結果(二維表)
> - 必須要 as, 因為對系統來說 select * from tableName 只是一條語句, 要把它做成一張表
```sql
mysql> select * from (select * from my_oname); -- 沒有 as
ERROR 1248 (42000): Every derived table must have its own alias
mysql> select * from (select * from my_oname) as o;
+----+-----------+-------+
| id | num | name |
+----+-----------+-------+
| 1 | NUM: 0001 | GODJJ |
| 2 | NUM: 0002 | GEAR |
| 3 | NUM: 0003 | BEBE |
+----+-----------+-------+
3 rows in set (0.00 sec)
```
### where 子句
> - 用來判斷數據, 篩選數據
> - where 子句返回 0=false, 1=true
> - 因為 MySQL 沒有Boolean, 所以 0 對 MySQL有特殊意義,
> - 所以 enum 排序等的都是從 1 開始
> - 判斷條件:
> - 比較運算符:
> `>` `<` `>=` `<=` `!=` `<>`(不等於)
> `like` `between and` `in / not in`
> - 邏輯運算符:
> `&&`(and) `||`(or) `!`(not, 取反)
> - where 原理:
> - where 是唯一一個直接從磁盤獲取數據時,就開始判斷條件
> - where 之後的都是操作內存的數據而非磁盤數據
> - where 從磁盤取出一條紀錄, 開始進行 where 判斷,
> true 就保存到內存, false 就丟掉
> - 優點: 不會讓無效數據佔用內存
> #### 準備測試用數據
> - Note.
> - `rand()` : 產生 0~1 隨機數
> - `floor()` : 向下取整
> - `ceil()` : 向上取整
```sql
mysql> select * from my_oname;
+----+-----------+-------+
| id | num | name |
+----+-----------+-------+
| 1 | NUM: 0001 | GODJJ |
| 2 | NUM: 0002 | GEAR |
| 3 | NUM: 0003 | BEBE |
+----+-----------+-------+
3 rows in set (0.00 sec)
mysql> -- 插入字段
mysql> alter table my_oname add age tinyint unsigned;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> alter table my_oname add height tinyint unsigned;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> select * from my_oname;
+----+-----------+-------+------+--------+
| id | num | name | age | height |
+----+-----------+-------+------+--------+
| 1 | NUM: 0001 | GODJJ | NULL | NULL |
| 2 | NUM: 0002 | GEAR | NULL | NULL |
| 3 | NUM: 0003 | BEBE | NULL | NULL |
+----+-----------+-------+------+--------+
3 rows in set (0.00 sec)
mysql> -- 插入數據
mysql> update my_oname set age = floor(rand()*10 + 20), height = floor(rand()*20+170);
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3 Changed: 3 Warnings: 0
mysql> select * from my_oname;
+----+-----------+-------+------+--------+
| id | num | name | age | height |
+----+-----------+-------+------+--------+
| 1 | NUM: 0001 | GODJJ | 22 | 184 |
| 2 | NUM: 0002 | GEAR | 29 | 180 |
| 3 | NUM: 0003 | BEBE | 27 | 170 |
+----+-----------+-------+------+--------+
3 rows in set (0.00 sec)
```
> #### 練習1.
> - 找到 id 為 1 或 3 的數據
```sql
mysql> -- 邏輯判斷
mysql> select * from my_oname where id = 1 || id = 3;
+----+-----------+-------+------+--------+
| id | num | name | age | height |
+----+-----------+-------+------+--------+
| 1 | NUM: 0001 | GODJJ | 22 | 184 |
| 3 | NUM: 0003 | BEBE | 27 | 170 |
+----+-----------+-------+------+--------+
2 rows in set, 1 warning (0.00 sec)
mysql> -- in 代表落在集合中
mysql> select * from my_oname where id in (1,3);
+----+-----------+-------+------+--------+
| id | num | name | age | height |
+----+-----------+-------+------+--------+
| 1 | NUM: 0001 | GODJJ | 22 | 184 |
| 3 | NUM: 0003 | BEBE | 27 | 170 |
+----+-----------+-------+------+--------+
2 rows in set (0.00 sec)
```
> #### 練習2.
> - 找出年齡在 (25~30) 的數據
> - `between` 的區間左邊值必須大於右邊
```sql
mysql> -- 邏輯運算
mysql> select * from my_oname where age >= 25 && age <= 30;
+----+-----------+------+------+--------+
| id | num | name | age | height |
+----+-----------+------+------+--------+
| 2 | NUM: 0002 | GEAR | 29 | 180 |
| 3 | NUM: 0003 | BEBE | 27 | 170 |
+----+-----------+------+------+--------+
2 rows in set, 1 warning (0.00 sec)
mysql> -- 在某區間之間
mysql> select * from my_oname where age between 25 and 30;
+----+-----------+------+------+--------+
| id | num | name | age | height |
+----+-----------+------+------+--------+
| 2 | NUM: 0002 | GEAR | 29 | 180 |
| 3 | NUM: 0003 | BEBE | 27 | 170 |
+----+-----------+------+------+--------+
2 rows in set (0.01 sec)
mysql> -- between 區間的左值必須大於右值
mysql> -- 因為between 會將左值轉成 >= 右值轉乘 <=
mysql> -- age >= 30 && age <= 25, 當然啥都找不到
mysql> select * from my_oname where age between 30 and 25;
Empty set (0.00 sec)
```
> #### where 1
> - 不篩選的意思, 寫這個通常是為了語句完整性
```sql
mysql> select * from my_oname where 1;
+----+-----------+-------+------+--------+
| id | num | name | age | height |
+----+-----------+-------+------+--------+
| 1 | NUM: 0001 | GODJJ | 22 | 184 |
| 2 | NUM: 0002 | GEAR | 29 | 180 |
| 3 | NUM: 0003 | BEBE | 27 | 170 |
+----+-----------+-------+------+--------+
3 rows in set (0.00 sec)
```
### GROUP BY
`group by 字段名`
> - 分組的意思, 根據字段進行分組
> - 分組是為了統計, SQL 提供了一些函數
> - 分組後, 每組只會產生一條統計數據
> - `count()` : 統計分組後的紀錄數, 可以使用兩種參數:
> - `*`
> - `字段名`: 統計對應的字段, NULL不統計
> - `Max()` : 統計每組最大值
> - `Min()` : 統計每組最小值
> - `avg()` : 統計平均值
> - `sum()` : 統計和
> - `group_concat()` : 對分組結果中的某字段進行字符串連接
```sql
mysql> select * from my_oname;
+----+-----------+-------+------+--------+--------+
| id | num | name | age | height | sex |
+----+-----------+-------+------+--------+--------+
| 1 | NUM: 0001 | GODJJ | 22 | 184 | male |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female |
+----+-----------+-------+------+--------+--------+
5 rows in set (0.00 sec)
mysql> select sex, count(*), max(height), min(height), avg(height), sum(age) from my_oname group by sex;
+--------+----------+-------------+-------------+-------------+----------+
| sex | count(*) | max(height) | min(height) | avg(height) | sum(age) |
+--------+----------+-------------+-------------+-------------+----------+
| male | 3 | 184 | 170 | 178.0000 | 78 |
| female | 2 | 179 | 174 | 176.5000 | 33 |
+--------+----------+-------------+-------------+-------------+----------+
2 rows in set (0.00 sec)
```
```sql
mysql> select * from my_oname;
+----+-----------+-------+------+--------+--------+
| id | num | name | age | height | sex |
+----+-----------+-------+------+--------+--------+
| 1 | NUM: 0001 | GODJJ | NULL | 184 | male |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female |
+----+-----------+-------+------+--------+--------+
5 rows in set (0.00 sec)
mysql> -- count(字段名)來統計字段時, null 不統計
mysql> -- 29+27/2 = 28 => 做平均時, 分母是以有統計的字段數來除
mysql> select sex, count(*), count(age), avg(age) from my_oname group by sex;
+--------+----------+------------+----------+
| sex | count(*) | count(age) | avg(age) |
+--------+----------+------------+----------+
| male | 3 | 2 | 28.0000 |
| female | 2 | 2 | 16.5000 |
+--------+----------+------------+----------+
2 rows in set (0.00 sec)
```
> - 多字段分組:
> `group by 字段1, 字段2`
> - 先對一個字段進行分組後, 再對分組後的結果進行其他字段分組
```sql
mysql> select * from my_oname;
+----+-----------+--------+------+--------+--------+------+
| id | num | name | age | height | sex | c_id |
+----+-----------+--------+------+--------+--------+------+
| 1 | NUM: 0001 | GODJJ | NULL | 184 | male | 2 |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 |
+----+-----------+--------+------+--------+--------+------+
6 rows in set (0.00 sec)
mysql> -- 先依據班級分組, 再針對每班分出性別
mysql> select c_id,sex,count(*),group_concat(name) from my_oname group by c_id, sex;
+------+--------+----------+--------------------+
| c_id | sex | count(*) | group_concat(name) |
+------+--------+----------+--------------------+
| 1 | male | 2 | GEAR,BEBE | -- 一班男生兩個
| 2 | male | 2 | GODJJ,BADBOY | -- 二班男生兩個
| 2 | female | 2 | BUBU,DIDI | -- 二班女生兩個
+------+--------+----------+--------------------+
3 rows in set (0.01 sec)
mysql> -- group_concat(name) 對分組結果的name字段的數據進行字符串連接
```
> #### 回溯統計
> `with rollup`
> - 在每次分組的結果上進行總統計
> - 回朔統計的分組字段為 NULL
> - 分組統計的結果是向下統計(粉組後的各項統計), 而回朔統計則為向上統計(分組前的統計)
> - 第一次分組: 會有一次回朔
```sql
mysql> select * from my_oname;
+----+-----------+--------+------+--------+--------+------+
| id | num | name | age | height | sex | c_id |
+----+-----------+--------+------+--------+--------+------+
| 1 | NUM: 0001 | GODJJ | NULL | 184 | male | 2 |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 |
| 7 | x | 小明 | 20 | 179 | male | 3 |
| 8 | x | 小花 | 20 | 179 | female | 3 |
+----+-----------+--------+------+--------+--------+------+
mysql> -- 用班級來分組
mysql> select c_id, count(*), avg(height) from my_oname group by c_id with rollup;
+------+----------+-------------+
| c_id | count(*) | avg(height) |
+------+----------+-------------+
| 1 | 2 | 175.0000 | -- 一班有兩人, 平均身高 175
| 2 | 4 | 176.0000 | -- 二班有四人, 平均身高 176
| 3 | 2 | 179.0000 | -- 三班有兩人, 平均身高 179
| NULL | 8 | 176.5000 | -- 全校共八人, 平均身高 176.5
+------+----------+-------------+
4 rows in set (0.00 sec)
```
> - 第二次分組會視組數而定
> - 以下列為例
> 
> - 先將資料以班級分類, 總共三班(一次回朔)
> - 再依性別分類, 總共兩性(三筆回朔, 三個班分類), 共四筆
```sql
mysql> select c_id,sex, count(*), avg(height) from my_oname group by c_id, sex with rollup;
+------+--------+----------+-------------+
| c_id | sex | count(*) | avg(height) |
+------+--------+----------+-------------+
| 1 | male | 2 | 175.0000 | -- 一班男生兩人, 平均身高 175
| 1 | NULL | 2 | 175.0000 | -- 一班總共兩人,平均身高 175
| 2 | male | 2 | 175.5000 | -- 二班男生兩人, 平均身高 175.5
| 2 | female | 2 | 176.5000 | -- 二班女生兩人, 平均身高 176.5
| 2 | NULL | 4 | 176.0000 | -- 二班總共四人, 平均身高 176
| 3 | male | 1 | 179.0000 | -- 三班男生一人, 平均身高 179
| 3 | female | 1 | 179.0000 | -- 三班女生一人, 平均身高 179
| 3 | NULL | 2 | 179.0000 | -- 三班總共兩人, 平均身高 179
| NULL | NULL | 8 | 176.5000 |
+------+--------+----------+-------------+
9 rows in set (0.00 sec)
```
> - 5.7 版本後多了一個 `ONLY_FULL_GROUP_BY` 來嚴格執行 SQL92
> - `select oo from tableName` 的 oo 叫做 `target list`
> - `group by oo` 的 xx 叫做 `group by column`
> - 這兩的欄位必須完全對應, 否則報錯
> - 覺得麻煩就自己用 set 把它改掉就好了
> - 砍掉
> `SET GLOBAL sql_mode = '除了 ONLY_FULL_GROUP_BY 的都填進去'`
> `SET SESSION sql_mode = '除了 ONLY_FULL_GROUP_BY 的都填進去'`
> `SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY,',''));`
> - 加回去
> `SET sql_mode=(SELECT CONCAT(@@sql_mode,',ONLY_FULL_GROUP_BY'));`
```sql
mysql> select @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
```
```txt
# 查到的其他模式意思, 先複製, 沒空消化
1.STRICT_TRANS_TABLES: 在该模式下,如果一个值不能插入到一个事务表中,则中断当前的操作,对非事务表不做限制
3.NO_ZERO_IN_DATE: 在严格模式下,不允许日期和月份为零
5.NO_ZERO_DATE: 设置该值,mysql数据库不允许插入零日期,插入零日期会抛出错误而不是警告。
7.ERROR_FOR_DIVISION_BY_ZERO:在INSERT或UPDATE过程中,如果数据被零除,则产生错误而非警告。如 果未给出该模式,那么数据被零除时MySQL返回NULL
9.NO_AUTO_CREATE_USER: 禁止GRANT创建密码为空的用户
11.NO_ENGINE_SUBSTITUTION: 如果需要的存储引擎被禁用或未编译,那么抛出错误。不设置此值时,用默认的存储引擎替代,并抛出一个异常
13.PIPES_AS_CONCAT: 将"||"视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样的,也和字符串的拼接函数Concat相类似
15.ANSI_QUOTES: 启用ANSI_QUOTES后,不能用双引号来引用字符串,因为它被解释为识别符
17.NO_AUTO_VALUE_ON_ZERO: 该值影响自增长列的插入。默认设置下,插入0或NULL代表生成下一个自增长值。如果用户 希望插入的值为0,而该列又是自增长的,那么这个选项就有用了。
```
```sql
mysql> select * from my_oname;
+----+-----------+-------+------+--------+--------+
| id | num | name | age | height | sex |
+----+-----------+-------+------+--------+--------+
| 1 | NUM: 0001 | GODJJ | 22 | 184 | male |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female |
+----+-----------+-------+------+--------+--------+
5 rows in set (0.00 sec)
mysql> -- 沒開啟的 ONLY_FULL_GROUP_BY 的話是沒問題的
mysql> select * from my_oname group by sex;
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'test2.my_oname.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
mysql> select sex from my_oname group by sex;
+--------+
| sex |
+--------+
| male |
| female |
+--------+
2 rows in set (0.01 sec)
```
### having 子句
> - having 子句與 where 子句一樣, 都是進行判斷的
> - where 是針對磁盤數據進行判斷
> - having 是在內存進行判斷
> - having 幾乎能做到 where 能做的所有事情, 但 where 卻無法做 having 能做的所有事
> 因為大家操作的對象與範圍都不同
> - 但是能夠用 where 時就盡量用 where, 如此才能保證有效數據源進入內存,
> 如此能夠提高內存利用率, 減少在內存出現錯誤數據的概率, 效率較高
> - 分組統計(函數)的結果, 只有 having 能判斷, 因為分組是在 where 之後才分組
```sql
mysql> -- 求超過三人的班級
mysql> -- 此時就必須先統計每班人數, 才有辦法知道哪些班級超過三人
mysql> select c_id, count(*) from my_oname group by c_id having count(*) >= 3;
+------+----------+
| c_id | count(*) |
+------+----------+
| 2 | 4 |
+------+----------+
1 row in set (0.00 sec)
mysql> -- where 使用分組後資訊根本不可能, 因為where判斷的時候根本還沒分組
mysql> select c_id, count(*) from my_oname where count(*) group by c_id;
ERROR 1111 (HY000): Invalid use of group function
```
> - having 能夠使用別名, where 不能
> - 別名是字段進入內存才能產生
> - where 是直接取磁盤的數據, 名字只可能是字段名
```sql
mysql> -- 拿到 B 開頭的名字
mysql> select name as 名字 from my_oname having 名字 like 'B%';
+--------+
| 名字 |
+--------+
| BEBE |
| BUBU |
| BADBOY |
+--------+
3 rows in set (0.00 sec)
mysql> -- where 取數據時根本還沒有別名~~
mysql> select name as 名字 from my_oname where 名字 like 'B%';
ERROR 1054 (42S22): Unknown column '名字' in 'where clause'
```
### ORDER BY
`order by 字段名 [asc|desc]`
> - 排序
> - `asc` : 升序(默認)
> - `desc` : 降序
```sql
mysql> -- 升序
mysql> select * from my_oname order by c_id;
+----+-----------+--------+------+--------+--------+------+
| id | num | name | age | height | sex | c_id |
+----+-----------+--------+------+--------+--------+------+
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 |
| 1 | NUM: 0001 | GODJJ | NULL | 184 | male | 2 |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 |
| 7 | x | 小明 | 20 | 179 | male | 3 |
| 8 | x | 小花 | 20 | 179 | female | 3 |
+----+-----------+--------+------+--------+--------+------+
8 rows in set (0.00 sec)
mysql> -- 降序
mysql> select * from my_oname order by c_id desc;
+----+-----------+--------+------+--------+--------+------+
| id | num | name | age | height | sex | c_id |
+----+-----------+--------+------+--------+--------+------+
| 7 | x | 小明 | 20 | 179 | male | 3 |
| 8 | x | 小花 | 20 | 179 | female | 3 |
| 1 | NUM: 0001 | GODJJ | NULL | 184 | male | 2 |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 |
+----+-----------+--------+------+--------+--------+------+
8 rows in set (0.00 sec)
```
> - 多字段排序:
> - 先根據某字段進行排序
> - 在排好序的基礎上再繼續做下一個字段的排序
```sql
mysql> -- 按照班級排序 (單字段排序)
mysql> select * from my_oname order by c_id;
+----+-----------+--------+------+--------+--------+------+
| id | num | name | age | height | sex | c_id |
+----+-----------+--------+------+--------+--------+------+
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 |
| 1 | NUM: 0001 | GODJJ | NULL | 184 | male | 2 |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 |
| 7 | x | 小明 | 20 | 179 | male | 3 |
| 8 | x | 小花 | 20 | 179 | female | 3 |
+----+-----------+--------+------+--------+--------+------+
8 rows in set (0.00 sec)
mysql> -- 按照班級排序後, 再按照身高排序(多字段排序)
mysql> select * from my_oname order by c_id, height;
+----+-----------+--------+------+--------+--------+------+
| id | num | name | age | height | sex | c_id |
+----+-----------+--------+------+--------+--------+------+
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 |
| 1 | NUM: 0001 | GODJJ | NULL | 184 | male | 2 |
| 7 | x | 小明 | 20 | 179 | male | 3 |
| 8 | x | 小花 | 20 | 179 | female | 3 |
+----+-----------+--------+------+--------+--------+------+
8 rows in set (0.00 sec)
```
### LIMIT 子句
> - 限制結果的子句
> - Limit 有兩種使用方法
> - 限制數量
> `limit 長度(數據量, length)`
> - 用來限制長度(數據量)
```sql
mysql> select * from my_oname limit 2;
+----+-----------+-------+------+--------+------+------+
| id | num | name | age | height | sex | c_id |
+----+-----------+-------+------+--------+------+------+
| 1 | NUM: 0001 | GODJJ | NULL | 184 | male | 2 |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 |
+----+-----------+-------+------+--------+------+------+
2 rows in set (0.00 sec)
```
> - 限制起始位置與數據量
> `limit 起始位置(offset), 長度(數據量, length)`
> - 經常用來實現數據的分頁, 提升服務器響應效率, 減少資源浪費, 節省用戶時間
> - 每頁長度(length) 基本上不會變
> - offset = page-1 * length
> - 假設每夜十筆資料
> - 第一頁 `limit ((1-1)*10, 10)`
> - 第二頁 `limit ((2-1)*10, 10)`
```sql
mysql> select * from my_oname limit 0 ,2;
+----+-----------+-------+------+--------+------+------+
| id | num | name | age | height | sex | c_id |
+----+-----------+-------+------+--------+------+------+
| 1 | NUM: 0001 | GODJJ | NULL | 184 | male | 2 |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 |
+----+-----------+-------+------+--------+------+------+
2 rows in set (0.00 sec)
mysql>
mysql> select * from my_oname limit 2,2;
+----+-----------+------+------+--------+--------+------+
| id | num | name | age | height | sex | c_id |
+----+-----------+------+------+--------+--------+------+
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 |
+----+-----------+------+------+--------+--------+------+
2 rows in set (0.00 sec)
```
## 連接查詢
`表 join 表`
> - 將多張表依據指定字段進行紀錄連接
> - 連接查詢分成四類: 內連接、外連接、自然連接、交叉連接
> - `表 join 表` 是數據源, 如果做查詢操作(select)時, 是先連接後查詢
### 交叉連接(cross join)
> - 一張表取出一條數據去匹配另一張表的所有數據,
> - 匹配結果全部保留(Cartesian product), 結果沒啥鳥用
```sql
mysql> select * from my_oname join my_class;
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
| id | num | name | age | height | sex | c_id | id | c_name | subject |
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 1 | 三年二班 | JS |
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 2 | 一年三班 | C |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 1 | 三年二班 | JS |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 2 | 一年三班 | C |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 1 | 三年二班 | JS |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 2 | 一年三班 | C |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | 1 | 三年二班 | JS |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | 2 | 一年三班 | C |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 | 1 | 三年二班 | JS |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 | 2 | 一年三班 | C |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | 1 | 三年二班 | JS |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | 2 | 一年三班 | C |
| 7 | x | 小明 | 20 | 179 | male | 3 | 1 | 三年二班 | JS |
| 7 | x | 小明 | 20 | 179 | male | 3 | 2 | 一年三班 | C |
| 8 | x | 小花 | 20 | 179 | female | 3 | 1 | 三年二班 | JS |
| 8 | x | 小花 | 20 | 179 | female | 3 | 2 | 一年三班 | C |
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
16 rows in set (0.00 sec)
```
### 內連接
`表1 [inner] join 表2 on 表1.字段 = 表2.字段`
> - 從表1取出一條數據去匹配表2所有紀錄
> - 匹配的結果必須在 `on` 條件成立下才會保留
> - 當字段名具唯一性, 表名.字段的表名可省, 只是為了可讀性還是會寫
```sql
mysql> select * from my_oname inner join my_class on my_oname.c_id = my_class.id;
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
| id | num | name | age | height | sex | c_id | id | c_name | subject |
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 2 | 一年三班 | C |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 1 | 三年二班 | JS |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 1 | 三年二班 | JS |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | 2 | 一年三班 | C |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 | 2 | 一年三班 | C |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | 2 | 一年三班 | C |
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
6 rows in set (0.00 sec)
mysql> -- 如果字段名具唯一性(c_id), 表名可省(my_oname.c_id => c_id)
mysql> select * from my_oname inner join my_class on c_id = my_class.id;
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
| id | num | name | age | height | sex | c_id | id | c_name | subject |
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 2 | 一年三班 | C |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 1 | 三年二班 | JS |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 1 | 三年二班 | JS |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | 2 | 一年三班 | C |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 | 2 | 一年三班 | C |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | 2 | 一年三班 | C |
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
6 rows in set (0.00 sec)
mysql> -- on 篩選條件成立才會保留
mysql> select * from my_oname inner join my_class on my_oname.height = my_class.id;
Empty set (0.00 sec)
```
> - 從結果可看到一個問題,字段名重複(id)
> - 當名字太長或字段名重複時, 可以使用別名
```sql
mysql> select o.*, c.c_name, c.subject as c_subject from my_oname as o inner join my_class as c on o.c_id = c.id;
+----+-----------+--------+------+--------+--------+------+--------------+-----------+
| id | num | name | age | height | sex | c_id | c_name | c_subject |
+----+-----------+--------+------+--------+--------+------+--------------+-----------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 一年三班 | C |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 三年二班 | JS |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 三年二班 | JS |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | 一年三班 | C |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 | 一年三班 | C |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | 一年三班 | C |
+----+-----------+--------+------+--------+--------+------+--------------+-----------+
6 rows in set (0.00 sec)
```
> - 內連接可以沒有 on, 不過就等於全部保留(Cartesian product)
```sql
mysql> select * from my_oname inner join my_class;
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
| id | num | name | age | height | sex | c_id | id | c_name | subject |
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 1 | 三年二班 | JS |
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 2 | 一年三班 | C |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 1 | 三年二班 | JS |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 2 | 一年三班 | C |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 1 | 三年二班 | JS |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 2 | 一年三班 | C |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | 1 | 三年二班 | JS |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | 2 | 一年三班 | C |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 | 1 | 三年二班 | JS |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 | 2 | 一年三班 | C |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | 1 | 三年二班 | JS |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | 2 | 一年三班 | C |
| 7 | x | 小明 | 20 | 179 | male | 3 | 1 | 三年二班 | JS |
| 7 | x | 小明 | 20 | 179 | male | 3 | 2 | 一年三班 | C |
| 8 | x | 小花 | 20 | 179 | female | 3 | 1 | 三年二班 | JS |
| 8 | x | 小花 | 20 | 179 | female | 3 | 2 | 一年三班 | C |
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
16 rows in set (0.00 sec)
```
> - where 可以代替 on
> - 但是一般都用 on, 因為 on 的效率比較高
> - on 的匹配方式是根據索引匹配
> - where 的匹配方式是一條一條判斷 true 或 flase
```sql
mysql> select * from my_oname inner join my_class where my_oname.c_id = my_class.id;
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
| id | num | name | age | height | sex | c_id | id | c_name | subject |
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 2 | 一年三班 | C |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 1 | 三年二班 | JS |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 1 | 三年二班 | JS |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | 2 | 一年三班 | C |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | 2 | 2 | 一年三班 | C |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | 2 | 一年三班 | C |
+----+-----------+--------+------+--------+--------+------+----+--------------+---------+
6 rows in set (0.00 sec)
```
### 外連接(outer join)
`左表 left/right join 右表 on 左表.字段 = 右表.字段`
> - 外連接以某表為主, 取出所有紀錄去與另張表匹配
> - 外連接有主表
> - `left join` : 左外連接, 以左表為主表, 最終紀錄數最少不少於左表
> - `right join` : 右外連接, 以右表為主表,最終紀錄數最少不少於右表
> - 顯示結果: 左表數據在左邊, 右表數據在右邊
> - 不論匹配條件是否吻合,皆會保留
> - 匹配成功的數據會正確保留
> - 不匹配成功的數據會為 NULL
```sql
mysql> select * from my_class;
+----+--------------+---------+
| id | c_name | subject |
+----+--------------+---------+
| 1 | 三年二班 | JS |
| 2 | 一年三班 | C |
| 3 | 四年四班 | PHP |
+----+--------------+---------+
3 rows in set (0.00 sec)
mysql> select * from my_oname;
+----+-----------+--------+------+--------+--------+------+
| id | num | name | age | height | sex | c_id |
+----+-----------+--------+------+--------+--------+------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | NULL |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 |
| 7 | x | 小明 | 20 | 179 | male | 3 |
| 8 | x | 小花 | 20 | 179 | female | 3 |
+----+-----------+--------+------+--------+--------+------+
8 rows in set (0.00 sec)
mysql> -- 仔細看 my_oname.id = 5
mysql> -- 左連接 ( 以 my_oname 為主 )
mysql> select * from my_oname left join my_class on my_oname.c_id = my_class.id;
+----+-----------+--------+------+--------+--------+------+------+--------------+---------+
| id | num | name | age | height | sex | c_id | id | c_name | subject |
+----+-----------+--------+------+--------+--------+------+------+--------------+---------+
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 1 | 三年二班 | JS |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 1 | 三年二班 | JS |
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 2 | 一年三班 | C |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | 2 | 一年三班 | C |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | 2 | 一年三班 | C |
| 7 | x | 小明 | 20 | 179 | male | 3 | 3 | 四年四班 | PHP |
| 8 | x | 小花 | 20 | 179 | female | 3 | 3 | 四年四班 | PHP |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | NULL | NULL | NULL | NULL |
+----+-----------+--------+------+--------+--------+------+------+--------------+---------+
8 rows in set (0.01 sec)
mysql> -- 副表中沒有可匹配的數據時, 會為 null
mysql> -- 右連接 ( 以 my_class 為主)
mysql> select * from my_oname right join my_class on my_oname.c_id = my_class.id;
+------+-----------+--------+------+--------+--------+------+----+--------------+---------+
| id | num | name | age | height | sex | c_id | id | c_name | subject |
+------+-----------+--------+------+--------+--------+------+----+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 2 | 一年三班 | C |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 1 | 三年二班 | JS |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 1 | 三年二班 | JS |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | 2 | 一年三班 | C |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | 2 | 一年三班 | C |
| 7 | x | 小明 | 20 | 179 | male | 3 | 3 | 四年四班 | PHP |
| 8 | x | 小花 | 20 | 179 | female | 3 | 3 | 四年四班 | PHP |
+------+-----------+--------+------+--------+--------+------+----+--------------+---------+
7 rows in set (0.00 sec)
```
### 自然連接(natural join)
> - 系統以同名字段作為匹配條件, 多個同名都是
> - 自然連接可分為 自然內連接 與 自然外連接
> - 非常少用
> - 因為必須確保所有表的同名字段都代表同個意思
> - 因為內外連接就可以模擬了
> - 自然內連接
> `左表 natural join 右表`
```sql
mysql> select * from my_class;
+----+--------------+---------+
| id | name | subject |
+----+--------------+---------+
| 1 | 三年二班 | JS |
| 2 | 一年三班 | C |
| 3 | 四年四班 | PHP |
+----+--------------+---------+
3 rows in set (0.00 sec)
mysql> select * from my_oname;
+----+-----------+--------+------+--------+--------+------+
| id | num | name | age | height | sex | c_id |
+----+-----------+--------+------+--------+--------+------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | NULL |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 |
| 7 | x | 小明 | 20 | 179 | male | 3 |
| 8 | x | 小花 | 20 | 179 | female | 3 |
+----+-----------+--------+------+--------+--------+------+
8 rows in set (0.00 sec)
mysql> -- 這兩張表都有 id 跟 name 字段
mysql> select * from my_oname natural join my_class;
Empty set (0.00 sec)
mysql> -- 事實上這句話就等於
mysql> -- select * from my_oname join my_class
> -- on my_oname.id = my_class.id && my_oname.name = my_class.name;
mysql> -- 雖然 id 匹配的到, 但是 name 就沒有匹配結果
mysql> alter table my_class change name c_name varchar(20);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> -- 只剩 id 字段名相同
mysql> -- 下面這句話就等於
mysql> -- select * from my_oname join my_class on my_oname.id = my_class.id;
mysql> select * from my_oname natural join my_class;
+----+-----------+-------+------+--------+------+------+--------------+---------+
| id | num | name | age | height | sex | c_id | c_name | subject |
+----+-----------+-------+------+--------+------+------+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 三年二班 | JS |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 一年三班 | C |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 四年四班 | PHP |
+----+-----------+-------+------+--------+------+------+--------------+---------+
3 rows in set (0.01 sec)
mysql> -- 不過這結果事實上也沒啥鳥用, 因為兩張表的 id 代表的東西又不一樣, 合再一起幹嘛
```
> - 自然外連接
> `左表 natural left/right join 右表`
```sql
mysql> select * from my_oname natural left join my_class;
+----+-----------+--------+------+--------+--------+------+--------------+---------+
| id | num | name | age | height | sex | c_id | c_name | subject |
+----+-----------+--------+------+--------+--------+------+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 三年二班 | JS |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 一年三班 | C |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 四年四班 | PHP |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | NULL | NULL |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | NULL | NULL | NULL |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | NULL | NULL |
| 7 | x | 小明 | 20 | 179 | male | 3 | NULL | NULL |
| 8 | x | 小花 | 20 | 179 | female | 3 | NULL | NULL |
+----+-----------+--------+------+--------+--------+------+--------------+---------+
8 rows in set (0.00 sec)
```
> - 模擬自然連接
> - 直接寫
> - 使用`using(字段)`
> - 使用什麼來連接
> - 該連接條件會合併
```sql
mysql> -- 模擬自然內連接
mysql> select my_oname.*, my_class.c_name, my_class.subject from my_oname join my_class on my_oname.id = my_class.id;
+----+-----------+-------+------+--------+------+------+--------------+---------+
| id | num | name | age | height | sex | c_id | c_name | subject |
+----+-----------+-------+------+--------+------+------+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 三年二班 | JS |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 一年三班 | C |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 四年四班 | PHP |
+----+-----------+-------+------+--------+------+------+--------------+---------+
3 rows in set (0.00 sec)
mysql> select * from my_oname join my_class using(id);
+----+-----------+-------+------+--------+------+------+--------------+---------+
| id | num | name | age | height | sex | c_id | c_name | subject |
+----+-----------+-------+------+--------+------+------+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 三年二班 | JS |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 一年三班 | C |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 四年四班 | PHP |
+----+-----------+-------+------+--------+------+------+--------------+---------+
3 rows in set (0.00 sec)
mysql> -- 模擬自然外連接
mysql> select o.*, c.c_name, c.subject from my_oname as o left join my_class as c on o.id = c.id;
+----+-----------+--------+------+--------+--------+------+--------------+---------+
| id | num | name | age | height | sex | c_id | c_name | subject |
+----+-----------+--------+------+--------+--------+------+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 三年二班 | JS |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 一年三班 | C |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 四年四班 | PHP |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | NULL | NULL |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | NULL | NULL | NULL |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | NULL | NULL |
| 7 | x | 小明 | 20 | 179 | male | 3 | NULL | NULL |
| 8 | x | 小花 | 20 | 179 | female | 3 | NULL | NULL |
+----+-----------+--------+------+--------+--------+------+--------------+---------+
8 rows in set (0.00 sec)
mysql> select * from my_oname left join my_class using(id);
+----+-----------+--------+------+--------+--------+------+--------------+---------+
| id | num | name | age | height | sex | c_id | c_name | subject |
+----+-----------+--------+------+--------+--------+------+--------------+---------+
| 1 | NUM: 0001 | GODJJ | 30 | 184 | male | 2 | 三年二班 | JS |
| 2 | NUM: 0002 | GEAR | 29 | 180 | male | 1 | 一年三班 | C |
| 3 | NUM: 0003 | BEBE | 27 | 170 | male | 1 | 四年四班 | PHP |
| 4 | NUL: 0004 | BUBU | 15 | 174 | female | 2 | NULL | NULL |
| 5 | NUL: 0005 | DIDI | 18 | 179 | female | NULL | NULL | NULL |
| 6 | NUM: 0006 | BADBOY | 20 | 167 | male | 2 | NULL | NULL |
| 7 | x | 小明 | 20 | 179 | male | 3 | NULL | NULL |
| 8 | x | 小花 | 20 | 179 | female | 3 | NULL | NULL |
+----+-----------+--------+------+--------+--------+------+--------------+---------+
8 rows in set (0.00 sec)
```
## 外鍵 (foreign key)
> - 表中有字段 (非主鍵) 指向另外一張表的主鍵,該字段稱為外鍵
> - 外鍵要求字段必須先是個索引(普通索引),
> 如果字段本身沒有索引, 外鍵會先創建一個索引後才創建外鍵本身
> - 一張表可以有多個外鍵
> - 外鍵雖然很屌, 不過會造成數據難以控制,
> - 例如父表被兩張表引用, 且引用外鍵的子表設置模式不同, 操作父表主鍵數據時, 對於 A表可能符合規則而可以操作, 但對於 B表可能不符合規則, 造成操作父表主鍵失敗
> - 故外鍵會造成服務器控制數據的權限降低, 造成很多人不用外鍵
> - 例如因為被引用外鍵而造成無法刪除父表, 必須先幹掉所有引用才能幹掉父表
> ```sql
> mysql> drop table class2;
> ERROR 3730 (HY000): Cannot drop table 'class2' referenced by a foreign key constraint 'my_fore4_ibfk_1' on table 'my_fore4'.
> ```
### 新增外鍵
> - 可以在創建表時或創建表後新增, 但要考慮數據問題
> #### 創建表時
> `[constraint 外鍵名] foreign key(外鍵字段) references 外部表(主鍵字段)`
> - 在所有表字段之後寫
> - 外鍵名不可重複
```sql
mysql> desc my_class;
+---------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| c_name | varchar(20) | YES | | NULL | |
| subject | varchar(20) | YES | | NULL | |
+---------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)
mysql> create table my_fore(
-> id int primary key auto_increment,
-> name varchar(20),
-> c_id int,
-> foreign key(c_id) references my_class(id)
-> ) charset utf8;
Query OK, 0 rows affected, 1 warning (0.02 sec)
mysql> desc my_fore;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| c_id | int(11) | YES | MUL | NULL | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
mysql> -- 外鍵欄寫 MUL(multi-column) => 因為外鍵創建前先創建一個普通索引後再創建外鍵
mysql> show create table my_fore;
| Table | Create Table
| my_fore | CREATE TABLE `my_fore` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`c_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c_id` (`c_id`), -- 索引1 => KEY
CONSTRAINT `my_fore_ibfk_1` FOREIGN KEY (`c_id`) REFERENCES `my_class` (`id`) -- 索引2 => FOREIGN KEY
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
1 row in set (0.01 sec)
mysql> -- CONSTRAINT `my_fore_ibfk_1` => 外鍵名
mysql> -- FOREIGN KEY (`c_id`) => 外鍵字段
mysql> -- REFERENCES `my_class` (`id`) 外鍵字段
```
> #### 創建表後新增(修改表結構)
> `alter table tableName add [constraint 外鍵名] foreign key (外鍵字段) references 外部表 (主鍵字段)`
```sql
mysql> desc my_fore2;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(10) | YES | | NULL | |
| c_id | int(11) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)
mysql> alter table my_fore2 add constraint student_class foreign key (c_id) references my_class (id);
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc my_fore2;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(10) | YES | | NULL | |
| c_id | int(11) | YES | MUL | NULL | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
```
### 刪除外鍵 & 修改外鍵
`alter table tableName drop foreign key 外鍵名`
> - 外鍵不可修改, 只能刪掉重增
> - 刪除外鍵後, 不會反映在表結構上, 必須看表創建語句
```sql
mysql> show create table my_fore;
| Table | Create Table
| my_fore | CREATE TABLE `my_fore` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`c_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c_id` (`c_id`),
CONSTRAINT `my_fore_ibfk_1` FOREIGN KEY (`c_id`) REFERENCES `my_class` (`id`) -- 外鍵
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.01 sec)
mysql> alter table my_fore drop foreign key my_fore_ibfk_1;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table my_fore;
| Table | Create Table
| my_fore | CREATE TABLE `my_fore` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`c_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c_id` (`c_id`) -- 外鍵刪掉了
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
1 row in set (0.00 sec)
mysql> desc my_fore;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| c_id | int(11) | YES | MUL | NULL | | -- 表結構看不出來
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
```
### 外鍵效果
#### 對子表(外鍵所在的表)約束
> - 對子表進行增或改操作時, 如果對應的外鍵字段在父表中找不到匹配,系統就會報錯
```sql
mysql> select * from my_class;
+----+--------------+---------+
| id | c_name | subject |
+----+--------------+---------+
| 1 | 三年二班 | JS |
| 2 | 一年三班 | C |
| 3 | 四年四班 | PHP |
+----+--------------+---------+
3 rows in set (0.00 sec)
mysql> insert into my_fore2 values (null, 'GODJJ', 2);
Query OK, 1 row affected (0.00 sec)
mysql> insert into my_fore2 values (null, 'GODJJ', 4);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test2`.`my_fore2`, CONSTRAINT `student_class` FOREIGN KEY (`c_id`) REFERENCES `my_class` (`id`))
mysql> -- c_id 的 id 只有 123, 4無法匹配而失敗
```
#### 對父表(被引用的表)約束
> - 對父表主鍵進行刪或改時, 對應的主鍵被子表引用數據時, 操作會失敗
```sql
mysql> select * from my_class;
+----+--------------+---------+
| id | c_name | subject |
+----+--------------+---------+
| 1 | 三年二班 | JS |
| 2 | 一年三班 | C |
| 3 | 四年四班 | PHP |
+----+--------------+---------+
3 rows in set (0.00 sec)
mysql> select * from my_fore2;
+----+-------+------+
| id | name | c_id |
+----+-------+------+
| 1 | GODJJ | 2 |
+----+-------+------+
1 row in set (0.00 sec)
mysql> update my_class set id = 4 where id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> -- 因為 my_fore2.c_id 已經引用了 my_class.id = 2 的數據, 所以報錯
mysql> update my_class set id = 5 where id = 2;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`test2`.`my_fore2`, CONSTRAINT `student_class` FOREIGN KEY (`c_id`) REFERENCES `my_class` (`id`))
```
### 外鍵條件
> - 存儲引擎必須為 `innodb`, 如果非 `innodb`, 外鍵可創建成功, 但不會約束
> - 外鍵字段的字段類型必須與父表主鍵完全一致
> - 外鍵名不能重複
> - 如果創建外鍵的字段已經有數據, 必須保證該數據在父表可以對應
```sql
mysql> -- my_fore 沒有外鍵~
mysql> show create table my_fore;
| Table | Create Table
| my_fore | CREATE TABLE `my_fore` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`c_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c_id` (`c_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
1 row in set (0.00 sec)
mysql> select * from my_fore;
+----+-------+------+
| id | name | c_id |
+----+-------+------+
| 1 | GODJJ | 2 |
| 2 | GODJJ | 2 |
| 3 | GODJJ | 9 | -- c_id = 9 匹配 my_class 的 id 時會匹配不到
+----+-------+------+
3 rows in set (0.00 sec)
mysql> select * from my_class;
+----+--------------+---------+
| id | c_name | subject |
+----+--------------+---------+
| 2 | 一年三班 | C |
| 3 | 四年四班 | PHP |
| 4 | 三年二班 | JS |
| 6 | 可憐吶 | 1 |
+----+--------------+---------+
4 rows in set (0.00 sec)
mysql> -- 當我字段裡有數據時設置外鍵, 且數據在父表會匹配不到時, 操作失敗
mysql> alter table my_fore add foreign key (c_id) references my_class (id);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test2`.`#sql-e8d_8`, CONSTRAINT `my_fore_ibfk_1` FOREIGN KEY (`c_id`) REFERENCES `my_class` (`id`))
mysql> -- 數據改成可以匹配到的數值時, 就沒問題了
mysql> update my_fore set c_id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 3 Changed: 1 Warnings: 0
mysql> alter table my_fore add foreign key (c_id) references my_class (id);
Query OK, 3 rows affected (0.05 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> show create table my_fore;
| Table | Create Table
| my_fore | CREATE TABLE `my_fore` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`c_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c_id` (`c_id`),
CONSTRAINT `my_fore_ibfk_1` FOREIGN KEY (`c_id`) REFERENCES `my_class` (`id`) -- 設置成功
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
1 row in set (0.01 sec)
```
### 外鍵約束
> - 外鍵效果
> - 可以限制子表字段必須與父表主鍵匹配
> - 可以限制父表不能亂刪改主鍵
> - 問題: 如果我真的必須刪改呢?
> - 外鍵約束有三種
> - `district` (嚴格模式, 約束模式) : 被引用的父表主鍵對應的數據不能刪改, 預設
> - `cascade` (階層模式) : 被引用的父表主鍵對應的數據刪改時, 對應子表關聯數據跟著刪改
> - `set null` (置空模式) : 被引用的父表主鍵對應的數據刪改時, 對應子表關聯數據置空
> - `set null` 的前提: 外鍵必須允許為 null
> - 選擇模式應視情況而定, 但更新用階層刪除用置空為大宗
> - 假設我有需求要更改父表主鍵數據
> - 嚴格模式無法改
> - 階層模式下, 子表跟著連動 (較適合)
> - 置空模式下, 子表會置空
> - 假設我有需求要刪除父表主鍵數據
> - 嚴格模式無法改
> - 階層模式下, 子表跟著連動而刪除
> - 置空模式下, 子表會置空 (較適合)
> - 設置約束模式
> `foreign key (外鍵) references 父表 (主鍵) on delete 模式 on update 模式`
```sql
mysql> create table my_fore3(
-> id int primary key auto_increment,
-> name varchar(20),
-> c_id int,
-> foreign key (c_id) references my_class (id) on delete set null on update cascade
-> ) charset utf8;
Query OK, 0 rows affected, 1 warning (0.04 sec)
mysql> show create table my_fore3;
| Table | Create Table
| my_fore3 | CREATE TABLE `my_fore3` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`c_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c_id` (`c_id`),
CONSTRAINT `my_fore3_ibfk_1` FOREIGN KEY (`c_id`) REFERENCES `my_class` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
1 row in set (0.00 sec)
```
```sql
mysql> select * from my_class;
+----+--------------+---------+
| id | c_name | subject |
+----+--------------+---------+
| 2 | 一年三班 | C |
| 3 | 四年四班 | PHP |
| 4 | 三年二班 | JS |
| 6 | 可憐吶 | 1 |
+----+--------------+---------+
4 rows in set (0.00 sec)
mysql> select * from my_fore3;
+----+--------+------+
| id | name | c_id |
+----+--------+------+
| 1 | 大哥 | 2 |
| 2 | 小弟 | 3 |
+----+--------+------+
2 rows in set (0.00 sec)
mysql> -- 修改使用 cascade 模式下, 修改父表主鍵數據, 子表對應數據會跟著改
mysql> update my_class set id = 9 where id = 2;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from my_class;
+----+--------------+---------+
| id | c_name | subject |
+----+--------------+---------+
| 3 | 四年四班 | PHP |
| 4 | 三年二班 | JS |
| 6 | 可憐吶 | 1 |
| 9 | 一年三班 | C | -- 修改
+----+--------------+---------+
4 rows in set (0.00 sec)
mysql> select * from my_fore3;
+----+--------+------+
| id | name | c_id |
+----+--------+------+
| 1 | 大哥 | 9 | -- 連動
| 2 | 小弟 | 3 |
+----+--------+------+
2 rows in set (0.00 sec)
mysql> -- 刪除使用 set null 模式下, 刪除父表主鍵數據, 對應子表數據會為 null
mysql> delete from my_class where id = 3;
Query OK, 1 row affected (0.01 sec)
mysql> select * from my_class;
+----+--------------+---------+
| id | c_name | subject |
+----+--------------+---------+
| 4 | 三年二班 | JS |
| 6 | 可憐吶 | 1 |
| 9 | 一年三班 | C |
+----+--------------+---------+
3 rows in set (0.00 sec)
mysql> select * from my_fore3;
+----+--------+------+
| id | name | c_id |
+----+--------+------+
| 1 | 大哥 | 9 |
| 2 | 小弟 | NULL | -- null
+----+--------+------+
2 rows in set (0.00 sec)
```
```sql
mysql> -- set null 的前提為外鍵允許為 null
mysql> desc class2;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
mysql> desc my_fore4;
+--------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| c_id | int(11) | NO | | NULL | |
| c_name | varchar(20) | YES | | NULL | |
+--------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql> -- c_id 不允許為 null 時設置 set null
mysql> alter table my_fore4 add foreign key (c_id) references class2 (id) on delete set null;
ERROR 1830 (HY000): Column 'c_id' cannot be NOT NULL: needed in a foreign key constraint 'my_fore4_ibfk_1' SET NULL
mysql> -- 沒有設置 set null 就成功
mysql> alter table my_fore4 add foreign key (c_id) references class2 (id);
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0
```
## 聯合查詢
> - 將多次查詢 (多條 select) 進行拼接 (字段不會增加)
> - 意義:
> - 查詢同張表但需求不同, 例如一些數據要升序, 一些數據要降序
> - 多表查詢(表結構都相同, 數據結構也相同), 因為數據過於龐大, 常用於拆表查詢
> - 水平分表: 十條數據分成兩張表, 每張各五
> - 垂直分表: 十個字段拆成兩張表, 每張各五
### 基本語句
`select 語句1 union [union 選項] select 語句2...`
> - union 選項
> - all: 保留全部
> - distinct : 去重複, 預設
> - 每條語句的字段數必須一致
```sql
mysql> -- 字段數必須一致
mysql> desc my_stu;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| c_id | int(11) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
mysql> desc my_class;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
mysql> select * from my_class union select * from my_stu;
ERROR 1222 (21000): The used SELECT statements have a different number of columns
mysql> -- 數據類型並無要求
mysql> select id,name from my_class union select name, id from my_class;
+------+------+
| id | name |
+------+------+
| 1 | JS |
| 2 | GO |
| JS | 1 |
| GO | 2 |
+------+------+
4 rows in set (0.00 sec)
```
```sql
mysql> -- 去重
mysql> select * from my_class union select * from my_class;
+----+------+
| id | name |
+----+------+
| 1 | JS |
| 2 | GO |
+----+------+
2 rows in set (0.00 sec)
mysql> -- 不去重
mysql> select * from my_class union all select * from my_class;
+----+------+
| id | name |
+----+------+
| 1 | JS |
| 2 | GO |
| 1 | JS |
| 2 | GO |
+----+------+
4 rows in set (0.00 sec)
```
### order by
`(select 子句 order by 字段 [asc||desc] limit 長度) union (select 子句 order by 字段 [asc||desc] limit 長度)`
> - 聯合查詢中, order by 不能直接使用, 必須使用 () 包起來
> - 若要 order by 生效, 必須搭配 limit, limit 限定最大數即可
```sql
mysql> -- 聯合查詢用 order by時, 必須用 () 包起來
mysql> select * from my_stu where sex = 'male' order by age
-> union select * from my_stu where sex = 'female' order by age desc;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'union select * from my_stu where sex = 'female' order by age desc' at line 2
mysql> -- 可以聯合, 但 order by 沒有效果
mysql> (select * from my_stu where sex = 'male' order by age)
-> union (select * from my_stu where sex = 'female' order by age desc);
+----+-------+------+--------+------+
| id | name | c_id | sex | age |
+----+-------+------+--------+------+
| 1 | GODJJ | 2 | male | 21 |
| 2 | BEBE | 1 | male | 22 |
| 4 | KKBOX | 2 | male | 18 |
| 3 | MEME | 2 | female | 28 |
| 5 | MARY | 1 | female | 20 |
| 6 | MAY | 2 | female | 26 |
+----+-------+------+--------+------+
6 rows in set (0.00 sec)
mysql> -- 加 limit 後, order by 才有效果
mysql> (select * from my_stu where sex = 'male' order by age limit 9999) union (select * from my_stu where sex = 'female' order by age desc limit 9999);
+----+-------+------+--------+------+
| id | name | c_id | sex | age |
+----+-------+------+--------+------+
| 4 | KKBOX | 2 | male | 18 |
| 1 | GODJJ | 2 | male | 21 |
| 2 | BEBE | 1 | male | 22 |
| 3 | MEME | 2 | female | 28 |
| 6 | MAY | 2 | female | 26 |
| 5 | MARY | 1 | female | 20 |
+----+-------+------+--------+------+
6 rows in set (0.00 sec)
```
## 子查詢 (sub query)
> - 在查詢結果上進行查詢
> - 子查詢分類: 子查詢的分類只是把返回的結果或使用的情境分類, 純粹方便理解用
> - from 子查詢 : 子查詢在 from 後面
> - 表子查詢 : 子查詢結果為多行多列
> - where 子查詢 : 子查詢在 where 條件中
> - 標量子查詢 : 子查詢結果為一行一列
> - 列子查詢 : 子查詢結果為多行一列
> - 行子查詢 : 子查詢結果為一行多列(多行多列)
> - exists 子查詢 : 子查詢在 exists 裡面
### 標量子查詢
> - 返回的結果為一行一列
```sql
mysql> -- 標量子查詢: 結果只有一行一列
mysql> select id from my_class where name = 'JS';
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
```
```sql
mysql> select * from my_class;
+----+------+
| id | name |
+----+------+
| 1 | JS |
| 2 | GO |
+----+------+
2 rows in set (0.00 sec)
mysql> select * from my_stu;
+----+-------+------+--------+------+
| id | name | c_id | sex | age |
+----+-------+------+--------+------+
| 1 | GODJJ | 2 | male | 21 |
| 2 | BEBE | 1 | male | 22 |
| 3 | MEME | 2 | female | 28 |
| 4 | KKBOX | 2 | male | 18 |
| 5 | MARY | 1 | female | 20 |
| 6 | MAY | 2 | female | 26 |
+----+-------+------+--------+------+
6 rows in set (0.00 sec)
mysql> -- 應用: 我想要查詢 JS 班上所有學生
mysql> select * from my_stu where c_id = (select id from my_class where name = 'JS');
+----+------+------+--------+------+
| id | name | c_id | sex | age |
+----+------+------+--------+------+
| 2 | BEBE | 1 | male | 22 |
| 5 | MARY | 1 | female | 20 |
+----+------+------+--------+------+
2 rows in set (0.01 sec)
```
### 列子查詢
> - 返回的結果為單行多列
> - 由於返回的結果有多個值, 不能直接 `=`, 一般都是用 `in` 來判斷
> - 其他類似的判斷條件關鍵字 `any` `some` `all`
> - `in` == `=any` == `=some`
> - `!=any` == `!=some`
> - `!=any` 本來想表達的是不等於全部值, 也就是全選的意思
> - 只是在英文的語意上發現怪怪的, 所以後來再多一個 some
> - 也就是說 any 跟 some 是一模一樣的東西
> - `not in` == `!=any`
```sql
mysql> select id from my_class;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.00 sec)
```
```sql
mysql> select * from my_class;
+----+------+
| id | name |
+----+------+
| 1 | JS |
| 2 | GO |
+----+------+
2 rows in set (0.00 sec)
mysql> select * from my_stu;
+----+-------+------+--------+------+
| id | name | c_id | sex | age |
+----+-------+------+--------+------+
| 1 | GODJJ | 2 | male | 21 |
| 2 | BEBE | 1 | male | 22 |
| 3 | MEME | 2 | female | 28 |
| 4 | KKBOX | 2 | male | 18 |
| 5 | MARY | 1 | female | 20 |
| 6 | MAY | 2 | female | 26 |
| 7 | GOGO | 9 | male | 28 |
+----+-------+------+--------+------+
7 rows in set (0.00 sec)
mysql> -- 應用: 查出所有在 my_class 的學員資訊
mysql> select * from my_stu where c_id in (select id from my_class);
+----+-------+------+--------+------+
| id | name | c_id | sex | age |
+----+-------+------+--------+------+
| 1 | GODJJ | 2 | male | 21 |
| 2 | BEBE | 1 | male | 22 |
| 3 | MEME | 2 | female | 28 |
| 4 | KKBOX | 2 | male | 18 |
| 5 | MARY | 1 | female | 20 |
| 6 | MAY | 2 | female | 26 |
+----+-------+------+--------+------+
6 rows in set (0.00 sec)
```
```sql
mysql> -- 肯定語句
mysql> select * from my_stu where c_id in (select id from my_class);
+----+-------+------+--------+------+
| id | name | c_id | sex | age |
+----+-------+------+--------+------+
| 1 | GODJJ | 2 | male | 21 |
| 2 | BEBE | 1 | male | 22 |
| 3 | MEME | 2 | female | 28 |
| 4 | KKBOX | 2 | male | 18 |
| 5 | MARY | 1 | female | 20 |
| 6 | MAY | 2 | female | 26 |
+----+-------+------+--------+------+
6 rows in set (0.00 sec)
mysql> select * from my_stu where c_id = any (select id from my_class);
+----+-------+------+--------+------+
| id | name | c_id | sex | age |
+----+-------+------+--------+------+
| 1 | GODJJ | 2 | male | 21 |
| 2 | BEBE | 1 | male | 22 |
| 3 | MEME | 2 | female | 28 |
| 4 | KKBOX | 2 | male | 18 |
| 5 | MARY | 1 | female | 20 |
| 6 | MAY | 2 | female | 26 |
+----+-------+------+--------+------+
6 rows in set (0.00 sec)
mysql> -- 任何 c_id 等於 1 或 2 就返回 true
mysql> select * from my_stu where c_id = some (select id from my_class);
+----+-------+------+--------+------+
| id | name | c_id | sex | age |
+----+-------+------+--------+------+
| 1 | GODJJ | 2 | male | 21 |
| 2 | BEBE | 1 | male | 22 |
| 3 | MEME | 2 | female | 28 |
| 4 | KKBOX | 2 | male | 18 |
| 5 | MARY | 1 | female | 20 |
| 6 | MAY | 2 | female | 26 |
+----+-------+------+--------+------+
6 rows in set (0.00 sec)
mysql> select * from my_stu where c_id = all (select id from my_class);
Empty set (0.00 sec)
mysql> -- 任何值都不可能等於每一個值
mysql> -- 否定語句
mysql> select * from my_stu where c_id not in (select id from my_class);
+----+------+------+------+------+
| id | name | c_id | sex | age |
+----+------+------+------+------+
| 7 | GOGO | 9 | male | 28 |
+----+------+------+------+------+
1 row in set (0.01 sec)
mysql> select * from my_stu where c_id != any (select id from my_class);
+----+-------+------+--------+------+
| id | name | c_id | sex | age |
+----+-------+------+--------+------+
| 1 | GODJJ | 2 | male | 21 |
| 2 | BEBE | 1 | male | 22 |
| 3 | MEME | 2 | female | 28 |
| 4 | KKBOX | 2 | male | 18 |
| 5 | MARY | 1 | female | 20 |
| 6 | MAY | 2 | female | 26 |
| 7 | GOGO | 9 | male | 28 |
+----+-------+------+--------+------+
7 rows in set (0.00 sec)
mysql> select * from my_stu where c_id != some (select id from my_class);
+----+-------+------+--------+------+
| id | name | c_id | sex | age |
+----+-------+------+--------+------+
| 1 | GODJJ | 2 | male | 21 |
| 2 | BEBE | 1 | male | 22 |
| 3 | MEME | 2 | female | 28 |
| 4 | KKBOX | 2 | male | 18 |
| 5 | MARY | 1 | female | 20 |
| 6 | MAY | 2 | female | 26 |
| 7 | GOGO | 9 | male | 28 |
+----+-------+------+--------+------+
7 rows in set (0.01 sec)
mysql> select * from my_stu where c_id != all (select id from my_class);
+----+------+------+------+------+
| id | name | c_id | sex | age |
+----+------+------+------+------+
| 7 | GOGO | 9 | male | 28 |
+----+------+------+------+------+
1 row in set (0.00 sec)
```
### 行子查詢
> - 返回的結果多行多列(一行多列)
```sql
mysql> select * from my_class;
+----+------+
| id | name |
+----+------+
| 1 | JS |
| 2 | GO |
+----+------+
2 rows in set (0.00 sec)
```
```sql
mysql> select * from my_stu;
+----+--------+------+--------+------+--------+
| id | name | c_id | sex | age | height |
+----+--------+------+--------+------+--------+
| 1 | GODJJ | 2 | male | 21 | 171 |
| 2 | BEBE | 1 | male | 22 | 163 |
| 3 | MEME | 2 | female | 28 | 166 |
| 4 | KKBOX | 2 | male | 18 | 178 |
| 5 | MARY | 1 | female | 20 | 172 |
| 6 | MAY | 2 | female | 26 | 170 |
| 7 | GOGO | 9 | male | 28 | 172 |
| 8 | HOWHOW | 2 | male | 79 | 200 |
+----+--------+------+--------+------+--------+
8 rows in set (0.00 sec)
mysql> -- 應用: 調出那個年齡最大且剛好最高的老人資料
mysql> -- 方法1. 用兩次標量子查詢
mysql> select * from my_stu where age = (select max(age) from my_stu) and height = (select max(height) from my_stu);
+----+--------+------+------+------+--------+
| id | name | c_id | sex | age | height |
+----+--------+------+------+------+--------+
| 8 | HOWHOW | 2 | male | 79 | 200 |
+----+--------+------+------+------+--------+
1 row in set (0.01 sec)
mysql> -- 方法2. 組出行元素來用行子查詢比較
mysql> select * from my_stu
-> where (age, height) -- 行元素
-> = (select max(age), max(height) from my_stu); -- 行子查詢
+----+--------+------+------+------+--------+
| id | name | c_id | sex | age | height |
+----+--------+------+------+------+--------+
| 8 | HOWHOW | 2 | male | 79 | 200 |
+----+--------+------+------+------+--------+
1 row in set (0.00 sec)
```
### 表子查詢
> - 子查詢返回的是多行多列的表, 使用該表當成數據源來使用
```sql
mysql> -- 暫時想不到情境, 寫個形式出來
mysql> select * from (select * from my_stu order by age) as a;
+----+--------+------+--------+------+--------+
| id | name | c_id | sex | age | height |
+----+--------+------+--------+------+--------+
| 4 | KKBOX | 2 | male | 18 | 178 |
| 5 | MARY | 1 | female | 20 | 172 |
| 1 | GODJJ | 2 | male | 21 | 171 |
| 2 | BEBE | 1 | male | 22 | 163 |
| 6 | MAY | 2 | female | 26 | 170 |
| 3 | MEME | 2 | female | 28 | 166 |
| 7 | GOGO | 9 | male | 28 | 172 |
| 8 | HOWHOW | 2 | male | 79 | 200 |
+----+--------+------+--------+------+--------+
8 rows in set (0.00 sec)
```
> - 把返回來的二維表再做成一個表使用
### exists 子查詢
`exists()`
> - `exists()` 返回的是 0 或 1, 表達是否存在的意思
> - 通常用在跨表上, 接在 where 之後
```sql
mysql> select exists(select * from my_stu where id = 2);
+-------------------------------------------+
| exists(select * from my_stu where id = 2) | -- id = 2 的資料有存在
+-------------------------------------------+
| 1 | -- 返回 1
+-------------------------------------------+
1 row in set (0.00 sec)
mysql> select exists(select * from my_stu where id = 100);
+---------------------------------------------+
| exists(select * from my_stu where id = 100) | -- id = 100 的資料不存在
+---------------------------------------------+
| 0 | -- 返回 0
+---------------------------------------------+
1 row in set (0.00 sec)
```
```sql
mysql> -- 目前想不到情境, 形式寫一下
mysql> select * from my_stu where exists(select * from my_stu where id = 1);
+----+--------+------+--------+------+--------+
| id | name | c_id | sex | age | height |
+----+--------+------+--------+------+--------+
| 1 | GODJJ | 2 | male | 21 | 171 |
| 2 | BEBE | 1 | male | 22 | 163 |
| 3 | MEME | 2 | female | 28 | 166 |
| 4 | KKBOX | 2 | male | 18 | 178 |
| 5 | MARY | 1 | female | 20 | 172 |
| 6 | MAY | 2 | female | 26 | 170 |
| 7 | GOGO | 9 | male | 28 | 172 |
| 8 | HOWHOW | 2 | male | 79 | 200 |
+----+--------+------+--------+------+--------+
8 rows in set (0.00 sec)
mysql> -- 如果一班存在, 顯示全校資料
```
## 視圖 (view)
> - 視圖是一種有結構沒結果的虛擬表
> - 視圖結構來源來自於對應的基表產生(視圖數據來源)
### 視圖作用
> - 視圖可以節省 SQL 語句: 將重複執行的查詢語句儲存, 減少打錯的可能
> - 數據安全:
> - 視圖本身不帶數據, 刪了也沒差
> - 視圖經常用於多系統, 對外提供數據時, 可以隱藏關鍵或沒用的數據,
> 甚至每個字段名都可以是重命名過的
> - 可以通過視圖來客製化數據提供
### 創建視圖
`create view 視圖名字 as select 語句`
> - 如果創建多表視圖, 注意字段不能重複
> - 視圖創建後, 系統會在對應的數據庫文件下創建一個結構文件(frm)
```sql
mysql> -- 單表視圖
mysql> create view my_view as select * from my_class;
Query OK, 0 rows affected (0.02 sec)
mysql> -- 多表視圖
mysql> -- 字段名不能重複
mysql> create view my_view2 as select * from my_class left join my_stu on my_class.id = my_stu.c_id;
ERROR 1060 (42S21): Duplicate column name 'id' -- id 重複了
mysql> create view my_view2 as select my_stu.*, my_class.name as c_name from my_stu left join my_class on my_stu.c_id = my_class.id;
Query OK, 0 rows affected (0.01 sec)
```
### 查看視圖結構
> - 表查看的語句, 視圖都能用
> - 視圖在查看表創建語句時, table 可以改成 view
```sql
mysql> show create table my_view\G
*************************** 1. row ***************************
View: my_view
Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `my_view` AS select `my_class`.`id` AS `id`,`my_class`.`name` AS `name` from `my_class`
character_set_client: utf8mb4
collation_connection: utf8mb4_general_ci
1 row in set (0.00 sec)
mysql> show create view my_view\G
*************************** 1. row ***************************
View: my_view
Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `my_view` AS select `my_class`.`id` AS `id`,`my_class`.`name` AS `name` from `my_class`
character_set_client: utf8mb4
collation_connection: utf8mb4_general_ci
1 row in set (0.00 sec)
```
### 使用視圖
> - 視圖使用主要是為了查詢, 當作普通的表使用即可
> - 視圖的執行, 本質上就是執行封裝的 select 語句
```sql
mysql> select * from my_view;
+----+------+
| id | name |
+----+------+
| 1 | JS |
| 2 | GO |
+----+------+
2 rows in set (0.00 sec)
mysql> -- 本質上就是執行 select * from my_class;
mysql> select * from my_view2;
+----+--------+------+--------+------+--------+--------+
| id | name | c_id | sex | age | height | c_name |
+----+--------+------+--------+------+--------+--------+
| 2 | BEBE | 1 | male | 22 | 163 | JS |
| 5 | MARY | 1 | female | 20 | 172 | JS |
| 1 | GODJJ | 2 | male | 21 | 171 | GO |
| 3 | MEME | 2 | female | 28 | 166 | GO |
| 4 | KKBOX | 2 | male | 18 | 178 | GO |
| 6 | MAY | 2 | female | 26 | 170 | GO |
| 8 | HOWHOW | 2 | male | 79 | 200 | GO |
| 7 | GOGO | 9 | male | 28 | 172 | NULL |
+----+--------+------+--------+------+--------+--------+
8 rows in set (0.01 sec)
mysql> -- 本質上就是執行 select my_stu.*, my_class.name as c_name from my_stu left join my_class on my_stu.c_id = my_class.id;
mysql> -- 如果每次都要寫那麼長, 那去賣雞排比較好, 視圖本質就是用來封裝長 select
```
### 修改視圖
`alter view 視圖名字 as 新的 select 語句`
> - 視圖本身不能修改, 但是可以修改視圖的來源
```sql
mysql> select * from my_view;
+----+------+
| id | name |
+----+------+
| 1 | JS |
| 2 | GO |
+----+------+
2 rows in set (0.00 sec)
mysql> -- 修改視圖
mysql> alter view my_view as select * from my_stu;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from my_view;
+----+--------+------+--------+------+--------+
| id | name | c_id | sex | age | height |
+----+--------+------+--------+------+--------+
| 1 | GODJJ | 2 | male | 21 | 171 |
| 2 | BEBE | 1 | male | 22 | 163 |
| 3 | MEME | 2 | female | 28 | 166 |
| 4 | KKBOX | 2 | male | 18 | 178 |
| 5 | MARY | 1 | female | 20 | 172 |
| 6 | MAY | 2 | female | 26 | 170 |
| 7 | GOGO | 9 | male | 28 | 172 |
| 8 | HOWHOW | 2 | male | 79 | 200 |
+----+--------+------+--------+------+--------+
8 rows in set (0.00 sec)
```
### 刪除視圖
`drop view 視圖名字`
> - 視圖本身根本沒有數據, 所以刪除視圖對數據沒有任何影響
```sql
mysql> show tables;
+-----------------+
| Tables_in_test2 |
+-----------------+
| my_class |
| my_stu |
| my_view |
| my_view2 |
+-----------------+
4 rows in set (0.00 sec)
mysql> drop view my_view2;
Query OK, 0 rows affected (0.01 sec)
mysql> show tables;
+-----------------+
| Tables_in_test2 |
+-----------------+
| my_class |
| my_stu |
| my_view |
+-----------------+
3 rows in set (0.00 sec)
```
### 數據新增
> - 事實上視圖是可以對基表操作的
> - 數據新增條件:
> - 多表視圖無法新增數據
> - 如果基表有字段要求不能為空且無預設值, 而視圖沒有該字段可以填, 那當然填不進去
> 換言之, 視圖必須有基表不能為空的字段
```sql
mysql> --單表視圖插入數據
mysql> desc my_stu;
+---------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-----------------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| c_id | int(11) | YES | | NULL | |
| sex | enum('male','female') | YES | | NULL | |
| age | tinyint(3) unsigned | YES | | NULL | |
| height | tinyint(3) unsigned | YES | | NULL | |
| notNull | varchar(20) | NO | | NULL | | -- 這個字段不能為空
+---------+-----------------------+------+-----+---------+----------------+
7 rows in set (0.01 sec)
mysql> desc my_view; -- 這張視圖沒有該字段欄位
+--------+-----------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-----------------------+------+-----+---------+-------+
| id | int(11) | NO | | 0 | |
| name | varchar(20) | YES | | NULL | |
| c_id | int(11) | YES | | NULL | |
| sex | enum('male','female') | YES | | NULL | |
| age | tinyint(3) unsigned | YES | | NULL | |
| height | tinyint(3) unsigned | YES | | NULL | |
+--------+-----------------------+------+-----+---------+-------+
6 rows in set (0.00 sec)
mysql> -- 插不進去
mysql> insert into my_view values (null, 'TEST2', 20, 'male', 37, 109);
ERROR 1423 (HY000): Field of view 'test2.my_view' underlying table doesn't have a default value
mysql> - 把該欄位改成可以為空後
mysql> alter table my_stu modify notNull varchar(20);
Query OK, 0 rows affected (0.14 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> -- 數據就能寫進去了, 該欄位沒有預設值, 所以寫入 null
mysql> insert into my_view values (null, 'TEST2', 20, 'male', 37, 109);
Query OK, 1 row affected (0.01 sec)
mysql> select * from my_stu;
+----+--------+------+--------+------+--------+---------+
| id | name | c_id | sex | age | height | notNull |
+----+--------+------+--------+------+--------+---------+
| 1 | GODJJ | 2 | male | 21 | 171 | |
| 2 | BEBE | 1 | male | 22 | 163 | |
| 3 | MEME | 2 | female | 28 | 166 | |
| 4 | KKBOX | 2 | male | 18 | 178 | |
| 5 | MARY | 1 | female | 20 | 172 | |
| 6 | MAY | 2 | female | 26 | 170 | |
| 7 | GOGO | 9 | male | 28 | 172 | |
| 8 | HOWHOW | 2 | male | 79 | 200 | |
| 9 | TEST | 20 | male | 37 | 109 | |
| 10 | TEST2 | 20 | male | 37 | 109 | NULL |
+----+--------+------+--------+------+--------+---------+
10 rows in set (0.00 sec)
```
```sql
mysql> -- 多表視圖
mysql> desc my_view2; -- 這張是多表視圖
+--------+-----------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-----------------------+------+-----+---------+-------+
| id | int(11) | NO | | 0 | |
| name | varchar(20) | YES | | NULL | |
| c_id | int(11) | YES | | NULL | |
| sex | enum('male','female') | YES | | NULL | |
| age | tinyint(3) unsigned | YES | | NULL | |
| height | tinyint(3) unsigned | YES | | NULL | |
| c_name | varchar(20) | YES | | NULL | |
+--------+-----------------------+------+-----+---------+-------+
7 rows in set (0.00 sec)
mysql> -- 插入數據報錯
mysql> insert into my_view2 values (null, 'KK', 2, 'male', 100, 100, 'JAVA');
ERROR 1471 (HY000): The target table my_view2 of the INSERT is not insertable-into
```
### 刪除數據
`delete from 視圖名 [where]`
> - 多表視圖 (join 系列) 不能刪
```sql
mysql> select * from my_view2;
+----+-------+------+--------+------+--------+--------+
| id | name | c_id | sex | age | height | c_name |
+----+-------+------+--------+------+--------+--------+
| 2 | BEBE | 1 | male | 22 | 163 | JS |
| 5 | MARY | 1 | female | 20 | 172 | JS |
| 1 | GODJJ | 2 | male | 21 | 171 | GO |
| 3 | MEME | 2 | female | 28 | 166 | GO |
| 4 | KKBOX | 2 | male | 18 | 178 | GO |
| 6 | MAY | 2 | female | 26 | 170 | GO |
| 7 | GOGO | 9 | male | 28 | 172 | NULL |
| 10 | TEST2 | 20 | male | 37 | 109 | NULL |
+----+-------+------+--------+------+--------+--------+
8 rows in set (0.00 sec)
mysql> delete from my_view2 where id = 10;
ERROR 1288 (HY000): The target table my_view2 of the DELETE is not updatable
```
> - 單表視圖可以刪
```sql
mysql> select * from my_stu;
+----+--------+------+--------+------+--------+---------+
| id | name | c_id | sex | age | height | notNull |
+----+--------+------+--------+------+--------+---------+
| 1 | GODJJ | 2 | male | 21 | 171 | |
| 2 | BEBE | 1 | male | 22 | 163 | |
| 3 | MEME | 2 | female | 28 | 166 | |
| 4 | KKBOX | 2 | male | 18 | 178 | |
| 5 | MARY | 1 | female | 20 | 172 | |
| 6 | MAY | 2 | female | 26 | 170 | |
| 7 | GOGO | 9 | male | 28 | 172 | |
| 8 | HOWHOW | 2 | male | 79 | 200 | |
| 9 | TEST | 20 | male | 37 | 109 | |
| 10 | TEST2 | 20 | male | 37 | 109 | NULL |
+----+--------+------+--------+------+--------+---------+
10 rows in set (0.00 sec)
mysql> delete from my_view where id in (8,9);
Query OK, 2 rows affected (0.01 sec)
mysql> select * from my_stu;
+----+-------+------+--------+------+--------+---------+
| id | name | c_id | sex | age | height | notNull |
+----+-------+------+--------+------+--------+---------+
| 1 | GODJJ | 2 | male | 21 | 171 | |
| 2 | BEBE | 1 | male | 22 | 163 | |
| 3 | MEME | 2 | female | 28 | 166 | |
| 4 | KKBOX | 2 | male | 18 | 178 | |
| 5 | MARY | 1 | female | 20 | 172 | |
| 6 | MAY | 2 | female | 26 | 170 | |
| 7 | GOGO | 9 | male | 28 | 172 | |
| 10 | TEST2 | 20 | male | 37 | 109 | NULL |
+----+-------+------+--------+------+--------+---------+
8 rows in set (0.00 sec)
```
### 更新數據
> - 單表視圖可更新
```sql
mysql> select * from my_view;
+----+-------+------+--------+------+--------+
| id | name | c_id | sex | age | height |
+----+-------+------+--------+------+--------+
| 1 | GODJJ | 2 | male | 21 | 171 |
| 2 | BEBE | 1 | male | 22 | 163 |
| 3 | MEME | 2 | female | 28 | 166 |
| 4 | KKBOX | 2 | male | 18 | 178 |
| 5 | MARY | 1 | female | 20 | 172 |
| 6 | MAY | 2 | female | 26 | 170 |
| 7 | GOGO | 9 | male | 28 | 172 |
| 10 | TEST2 | 20 | male | 37 | 109 |
+----+-------+------+--------+------+--------+
8 rows in set (0.00 sec)
mysql> update my_view set height = 150 where id = 10;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from my_stu;
+----+-------+------+--------+------+--------+---------+
| id | name | c_id | sex | age | height | notNull |
+----+-------+------+--------+------+--------+---------+
| 1 | GODJJ | 2 | male | 21 | 171 | |
| 2 | BEBE | 1 | male | 22 | 163 | |
| 3 | MEME | 2 | female | 28 | 166 | |
| 4 | KKBOX | 2 | male | 18 | 178 | |
| 5 | MARY | 1 | female | 20 | 172 | |
| 6 | MAY | 2 | female | 26 | 170 | |
| 7 | GOGO | 9 | male | 28 | 172 | |
| 10 | TEST2 | 20 | male | 37 | 150 | NULL | -- 150 高
+----+-------+------+--------+------+--------+---------+
8 rows in set (0.00 sec)
```
> #### 更新限制
> `with check option`
> - 如果在創建視圖時限制了某些字段, 對視圖進行更新操作時, 系統會進行驗證:
> 必須保證更新後數據依然可以被查到, 否則失敗
> - 如果操作非視圖外的數據, 操作沒有任何效果
```sql
mysql> select * from my_stu;
+----+-------+------+--------+------+--------+---------+
| id | name | c_id | sex | age | height | notNull |
+----+-------+------+--------+------+--------+---------+
| 1 | GODJJ | 2 | male | 21 | 171 | |
| 2 | BEBE | 1 | male | 22 | 163 | |
| 3 | MEME | 2 | female | 28 | 166 | |
| 4 | KKBOX | 2 | male | 18 | 178 | |
| 5 | MARY | 1 | female | 20 | 172 | |
| 6 | MAY | 2 | female | 26 | 170 | |
| 7 | GOGO | 9 | male | 28 | 172 | |
| 10 | TEST2 | 20 | male | 37 | 150 | NULL |
+----+-------+------+--------+------+--------+---------+
8 rows in set (0.00 sec)
mysql> -- 創建視圖時, 限制年齡必須大於 25
mysql> create view my_view3 as select * from my_stu where age > 25 with check option;
Query OK, 0 rows affected (0.01 sec)
mysql> -- 創建的視圖
mysql> select * from my_view3;
+----+-------+------+--------+------+--------+---------+
| id | name | c_id | sex | age | height | notNull |
+----+-------+------+--------+------+--------+---------+
| 3 | MEME | 2 | female | 28 | 166 | |
| 6 | MAY | 2 | female | 26 | 170 | |
| 7 | GOGO | 9 | male | 28 | 172 | |
| 10 | TEST2 | 20 | male | 37 | 150 | NULL |
+----+-------+------+--------+------+--------+---------+
4 rows in set (0.00 sec)
mysql> -- 把年齡改成 20 , 由於低於 25, 改之後會查不到, 所以失敗
mysql> update my_view3 set age = 20 where id = 10;
ERROR 1369 (HY000): CHECK OPTION failed 'test2.my_view3'
mysql> -- 改成 30 就可以
mysql> update my_view3 set age = 30 where id = 10;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> -- 修改非視圖的數據時, 不會有任何效果
mysql> update my_view3 set age = 30 where id = 4;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0 -- Changed: 0
mysql> select * from my_stu;
+----+-------+------+--------+------+--------+---------+
| id | name | c_id | sex | age | height | notNull |
+----+-------+------+--------+------+--------+---------+
| 1 | GODJJ | 2 | male | 21 | 171 | |
| 2 | BEBE | 1 | male | 22 | 163 | |
| 3 | MEME | 2 | female | 28 | 166 | |
| 4 | KKBOX | 2 | male | 18 | 178 | | -- 還是 18
| 5 | MARY | 1 | female | 20 | 172 | |
| 6 | MAY | 2 | female | 26 | 170 | |
| 7 | GOGO | 9 | male | 28 | 172 | |
| 10 | TEST2 | 20 | male | 30 | 150 | NULL |
+----+-------+------+--------+------+--------+---------+
8 rows in set (0.00 sec)
```
### 視圖算法
`create [ALGORITHM = 視圖算法] view 視圖名字 as select 語句`
> - 視圖本身就是一組 select 語句, 那麼再次對視圖執行 select 語句時, 就會形成子查詢,
> 如果對視圖操作的 select 語句用了規定上比視圖的 select 語句還要前面的查詢語句
> 那麼應該先操作視圖 (數據源) 的語句, 還是按照原本規定的順序?
```sql
mysql> -- 創建一張視圖, 裡面帶有 having 判斷
mysql> create view my_view4 as select * from my_stu having age < 25;
Query OK, 0 rows affected (0.01 sec)
mysql> -- 對視圖進行查詢操作且帶有 group 操作時, 會產生先做 group 還是 having 的問題
mysql> -- 下面這句攤開來會長這樣:
mysql> -- select age from (select * from my_stu having age < 25) as tmp group by age;
mysql> -- 此時究竟是先做數據源的 select having, 還是先做 group?
mysql> select age from my_view4 group by age;
+------+
| age |
+------+
| 21 |
| 22 |
| 18 |
| 20 |
+------+
4 rows in set (0.00 sec)
```
> - 系統對此情況的解析方式, 分為三種
> - `undefined` :
> - 未定義(默認), 讓系統自行依情況選擇
> - 大部分情況下, 系統都會使用 `merge`
> - `temptable` :
> - 先執行視圖的 select 語句, 後執行外部查詢語句
> - 以上面的例子來說, 就是先 having 後 group
> - `merge` :
> - 將兩個查詢語句合併後查詢(只查詢一次, 效率較高)
> - 以上面的例子來說, 會將兩個語句合併而變成
> `select age from my_stu group by age having age < 25;`
> 而先 group 後 having
> - 只考慮是否達成想要的效果, 而想執行這類違反順序規定的語句時, 就改成 tempable,
> 其他時候默認即可, 因為系統比較愛 merge, 一般都會自動轉用這個