# 關聯式資料庫 基礎教學 ## 什麼是關聯式資料庫? **關聯式資料庫(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/