# 關聯式資料庫 基礎教學
## 什麼是關聯式資料庫?
**關聯式資料庫(RDBMS)**:全名為Relational Database Menagement System,是一種以關聯模型為基礎的資料庫系統,用於「儲存」和「管理」結構化的資料。在關聯式資料庫中,資料以表格的形式進行儲存,每個表格都由「列(直的)」和「行(橫的)」所組成,而這些資料都是有「關聯性」的,所以才會被歸類在同一張表裡。
### 關聯式資料庫的特點:
* 每個表格都會有固定的欄位名稱和資料類型
* 每個表格都會有一個或多個「**主鍵(PK,Primary Key)**」
* 遵循**ACID屬性(原子性、一致性、隔離性、持久性)**,確保資料的「完整性」和「可靠性」
* 允許參考其他表格的資料,只要在建立表格的時候設定「**外鍵(FK,Foreign Key**)」,就可以讓不同資料表的資料做關聯
### 常見的RDBMS
* MySQL
* Microsoft SQL Server
* Oracle Database
* PostgreSQL
* SQLite
### 舉例
舉個例子,現在有一張叫stu的表,裡面放的是學生的基本資料,所以資料表裡就會有學生的學號、姓名、年齡、班級等欄位,分別存放相對應的資料
* 資料型別:
* 學號:nchar(10) (主鍵)
* 姓名:nvarchar(20)
* 年齡:Int
* 班級:nvarchar(10)
| 學號(主鍵) | 姓名 | 年齡 | 班級 |
| ---------- | ---- | ---- | ------- |
| s1001 | 老王 | 23 | 資工四1 |
| s1002 | 老陳 | 21 | 資工二1 |
| s1003 | 老林 | 22 | 資工三1 |
那如果現在有一個學生借書系統(陽春版的),資料庫就會多一張叫book的表,供圖書館阿姨看目前圖書館的書被哪些學生借走
* 資料型別
* 書籍編號:nchar(10)
* 書名:nvarchar(50)
* 借書人學號:nchar(10)
| 書籍編號(主鍵) | 書名 | 借書人學號(外來鍵) |
| -------------- | ---------- | ------------------ |
| b1001 | 哈利波特 | s1001 |
| b1002 | 暮光之城 | s1001 |
| b1003 | 三國演義 | s1003 |
| b1004 | 波西傑克森 | s1001 |
| b1005 | 紅樓夢 | s1003 |
:::success
**Note:**
* book這張表本來只存放書籍資料,但阿姨想知道書是被哪位學生借走的,所以就會設一個「外鍵」,讓book這張表去參考到stu的資料,這樣才能在學生逾期未還書時,把書追回來
:::
### 有關聯式資料庫,那有非關聯式資料庫嗎?
答案是有的
**非關聯式資料庫(NoSQL)**:全名為:Not Only SQL,是為了解決RDBMS在某些場景下的限制,並提供更好的擴展性、靈活性和性能
格式:
| key | value |
| -------- | -------- |
| key_name | {key1: value2, key2: value2, ...} |
![](https://hackmd.io/_uploads/B1diztNa3.png)
圖片來源:https://medium.com/marketingdatascience/%E5%B8%B8%E7%94%A8%E7%9A%84%E8%B3%87%E6%96%99%E5%84%B2%E5%AD%98%E8%88%87%E5%88%86%E6%9E%90%E5%B7%A5%E5%85%B7-9a27b90f619
## SQL語法
為了怕大家環境不同,我選了一個可以線上練習SQL語法的平台
練習平台:https://sqliteonline.com/
進去後點擊MS SQL > Click to connect做連接,讓你模擬連上資料庫的動作
![](https://hackmd.io/_uploads/SkcjYeXT3.png)
連接成功的畫面:point_down:
![](https://hackmd.io/_uploads/SkvfAxXTh.png)
### 新增資料表
```sql=
-- 新增資料表的基本語法樣式
CREATE TABLE Stu
(
column1_name type1,
column2_name type2,
.
.
);
```
那我們就拿上面的例子來做示範,新增stu和book這兩張表
```sql=
CREATE TABLE stu
(
學號 nchar(10),
姓名 nvarchar(20) Not Null, --無論如何這個欄位的資料不能為空
年齡 INT,
班級 nvarchar(5),
PRIMARY KEY (學號) --設定學號為主鍵
);
CREATE TABLE book
(
書籍編號 nchar(10),
書名 nvarchar(50),
借書人學號 nchar(10),
PRIMARY KEY (書籍編號), --設定書籍編號為主鍵
FOREIGN KEY (借書人學號) REFERENCES stu(學號) --設定外來鍵,參考stu這張表
);
```
### INSERT
```sql=
-- 新增資料的基本語法樣式
INSERT INTO table_name (column1_name, column2_name, ...)
VALUES ('value1', 'value2', ...);
```
資料表建立好後,就把資料一併新增進去
```sql=
INSERT INTO stu (學號, 姓名, 年齡, 班級) VALUES
('s1001', N'老王', 23, N'資工四1'),
('s1002', N'老陳', 21, N'資工二1'),
('s1003', N'老林', 22, N'資工三1');
INSERT INTO book (書籍編號, 書名, 借書人學號) VALUES
('b1001', N'哈利波特', 's1001'),
('b1002', N'暮光之城', 's1001'),
('b1003', N'三國演義', 's1003'),
('b1004', N'波西傑克森', 's1001'),
('b1005', N'紅樓夢', 's1003');
```
如果欄位沒有特別設定Not Null,在新增時也可以不去輸入那個欄位的資料
```sql=
INSERT INTO stu (學號, 姓名, 年齡) VALUES ('s1004', N'老白', 18);
```
* return:
| 學號(主鍵) | 姓名 | 年齡 | 班級 |
| ---------- | ---- | ---- | ------- |
| s1001 | 老王 | 23 | 資工四1 |
| s1002 | 老陳 | 21 | 資工二1 |
| s1003 | 老林 | 22 | 資工三1 |
| s1004 | 老白 | 18 | NULL |
### SELECT
```sql=
-- 查詢資料的基本語法樣式
SELECT * FROM table_name; --*是全部欄位的意思
-- 指定查詢的欄位
SELECT column1_name, column2_name... FROM table_name;
-- 查找特定欄位的資料
SELECT column1_name, column2_name...
FROM table_name
WHERE column1_name='XXX';
```
可以先查詢全部的欄位,看看資料有沒有都新增成功
```sql=
SELECT * FROM stu;
SELECT * FROM book;
```
再來針對學號為s1001的資料做查詢
```sql=
SELECT 學號, 姓名, 班級
FROM stu
WHERE 學號='s1001';
```
* return:
| 學號 | 姓名 | 班級 |
| ----- | ---- | ------- |
| s1001 | 老王 | 資工四1 |
那如果阿姨現在想看s1001這個學生借了多少書,就一樣對WHERE下語法就好
```sql=
SELECT 書名, 借書人學號
FROM book
WHERE 借書人學號='s1001';
```
* return:
| 書名 | 借書人學號 |
| ---------- | ---------- |
| 哈利波特 | s1001 |
| 暮光之城 | s1001 |
| 波西傑克森 | s1001 |
### UPDATE
* **注意:warning::UPDATE和DELETE不使用WHERE的話,所有資料都會被更改(很嚴重)**
```sql=
-- 更新資料的基本語法樣式
UPDATE table_name
SET column1_name='value1', ..., ...
WHERE column?_name='value?';
```
假設我們現在知道老白上什麼班級了,我們就可以把他UPDATE上去
```sql=
UPDATE stu
SET 班級=N'老服一1'
WHERE 學號='s1004';
```
再用`SELECT*FROM stu`查看,就會看到老白的班級被成功更新上去
* return:
| 學號(主鍵) | 姓名 | 年齡 | 班級 |
| ---------- | ---- | ---- | ------- |
| s1001 | 老王 | 23 | 資工四1 |
| s1002 | 老陳 | 21 | 資工二1 |
| s1003 | 老林 | 22 | 資工三1 |
| s1004 | 老白 | 18 | 老服一1 |
### DELETE
```sql=
-- 刪除資料的基本語法樣式
DELETE FROM table_name WHERE column?_name='value';
```
如果圖書館阿姨現在不想把紅樓夢這本書放在圖書館了,就要把他從資料庫中刪除
```sql=
DELETE FROM book WHERE 書籍編號='b1005';
```
再用`SELECT * FROM book`查看,就會看到紅樓夢被成功刪除
* return:
| 書籍編號 | 書名 | 借書人學號 |
| -------- | ---------- | ---------- |
| b1001 | 哈利波特 | s1001 |
| b1002 | 暮光之城 | s1001 |
| b1003 | 三國演義 | s1003 |
| b1004 | 波西傑克森 | s1001 |
或者是老白剛開學就想休學,那一樣要把他從資料庫中刪除
```sql=
DELETE FROM stu WHERE 學號='s1004';
```
再用`SELECT * FROM stu`查看,就會看到老白的資料被成功刪除
### 額外例子
那如果今天想刪除學號s1003的資料,會怎樣呢?
```sql=
DELETE FROM stu WHERE 學號='s1003';
```
我既然會這樣問,那他當然是會報錯XD
![](https://hackmd.io/_uploads/r1sNOOVpn.png)
因為book這張表裡有參考著s1003的資料,這是所謂的「**外鍵約束**」,你必須先在book裡將有參考到s1003的資料刪除,才可以在stu裡刪除s1003的資料
```sql=
DELETE FROM book WHERE 書籍編號='b1003';
```
* `SELECT * FROM book` return:
| 書籍編號 | 書名 | 借書人學號 |
| -------- | ---------- | ---------- |
| b1001 | 哈利波特 | s1001 |
| b1002 | 暮光之城 | s1001 |
| b1004 | 波西傑克森 | s1001 |
那你現在到stu刪除s1003的資料,就可以成功刪除了
```sql=
DELETE FROM stu WHERE 學號='s1003';
```
* `SELECT * FROM stu` return:
| 學號(主鍵) | 姓名 | 年齡 | 班級 |
| ---------- | ---- | ---- | ------- |
| s1001 | 老王 | 23 | 資工四1 |
| s1002 | 老陳 | 21 | 資工二1 |
| s1004 | 老白 | 18 | 老服一1 |
### AND OR
```sql=
SELECT *
FROM stu
WHERE 學號='s1001' AND 學號='s1002';
SELECT *
FROM stu
WHERE 學號='s1001' OR 學號='s1002';
```
* return:
| 學號(主鍵) | 姓名 | 年齡 | 班級 |
| ---------- | ---- | ---- | ------- |
| s1001 | 老王 | 23 | 資工四1 |
| s1002 | 老陳 | 21 | 資工二1 |
### LIKE
```sql=
SELECT *
FROM stu
WHERE 姓名 LIKE N'%王%';
```
* return:
| 學號(主鍵) | 姓名 | 年齡 | 班級 |
| ---------- | ---- | ---- | ------- |
| s1001 | 老王 | 23 | 資工四1 |
### ORDER BY - 排序
```sql=
-- 排序的基本語法樣式
SELECT *
FROM table_name
WHERE 條件1、條件2、...
ORDER BY "欲排序的column_name" [ASC, DESC]; --ASC遞增、DESC遞減
```
```sql=
SELECT *
FROM stu
WHERE 姓名 LIKE N'%老%'
ORDER BY 學號 DESC;
```
* return
| 學號(主鍵) | 姓名 | 年齡 | 班級 |
| ---------- | ---- | ---- | ------- |
| s1003 | 老林 | 22 | 資工三1 |
| s1002 | 老陳 | 21 | 資工二1 |
| s1001 | 老王 | 23 | 資工四1 |
### join
* **注意:warning::join的WHERE會變成ON**
常見的join方式:
* 1.inner Join 內部合併查詢
* 2.left (outer) Join 左外部合併查詢
* 3.right (outer) Join 右外部合併查詢
* 4.full (outer) Join 完全外部合併查詢
* 5.cross Join 交叉合併查詢
* 參考資料:https://ithelp.ithome.com.tw/articles/10215741
```sql=
-- 這樣也算一個join,但會很雜
SELECT *
FROM stu, book;
```
#### inner join
```sql=
SELECT *
FROM book Inner Join stu
ON stu.學號=book.借書人學號;
```
* return:
| 書籍編號 | 書名 | 借書人學號 | 學號(主鍵) | 姓名 | 年齡 | 班級 |
| -------- | ---------- | ---------- | ---------- | ---- | ---- | ------- |
| b1001 | 哈利波特 | s1001 | s1001 | 老王 | 23 | 資工四1 |
| b1002 | 暮光之城 | s1001 | s1001 | 老王 | 23 | 資工四1 |
| b1004 | 波西傑克森 | s1001 | s1001 | 老王 | 23 | 資工四1 |
#### left join
```sql=
SELECT *
FROM book left Join stu
ON stu.學號=book.借書人學號;
```
* return:
| 書籍編號 | 書名 | 借書人學號 | 學號(主鍵) | 姓名 | 年齡 | 班級 |
| -------- | ---------- | ---------- | ---------- | ---- | ---- | ------- |
| b1001 | 哈利波特 | s1001 | s1001 | 老王 | 23 | 資工四1 |
| b1002 | 暮光之城 | s1001 | s1001 | 老王 | 23 | 資工四1 |
| b1004 | 波西傑克森 | s1001 | s1001 | 老王 | 23 | 資工四1 |
#### right join
```sql=
SELECT *
FROM book right Join stu
ON stu.學號=book.借書人學號;
```
* return:
| 書籍編號 | 書名 | 借書人學號 | 學號(主鍵) | 姓名 | 年齡 | 班級 |
| -------- | ---------- | ---------- | ---------- | ---- | ---- | ------- |
| b1001 | 哈利波特 | s1001 | s1001 | 老王 | 23 | 資工四1 |
| b1002 | 暮光之城 | s1001 | s1001 | 老王 | 23 | 資工四1 |
| b1004 | 波西傑克森 | s1001 | s1001 | 老王 | 23 | 資工四1 |
| Null | Null | Null | s1002 | 老陳 | 21 | 資工二1 |
#### full join
```sql=
SELECT *
FROM book Full Join stu
ON stu.學號=book.借書人學號;
```
* return:
| 書籍編號 | 書名 | 借書人學號 | 學號(主鍵) | 姓名 | 年齡 | 班級 |
| -------- | ---------- | ---------- | ---------- | ---- | ---- | ------- |
| b1001 | 哈利波特 | s1001 | s1001 | 老王 | 23 | 資工四1 |
| b1002 | 暮光之城 | s1001 | s1001 | 老王 | 23 | 資工四1 |
| b1004 | 波西傑克森 | s1001 | s1001 | 老王 | 23 | 資工四1 |
| Null | Null | Null | s1002 | 老陳 | 21 | 資工二1 |
### 更多的SQL語法教學:https://www.fooish.com/sql/