# SQL ## Concept 資料查詢語言(**DQL**, Data Query Language): **SELECT** 資料操作語言(**DML**, Data Manipulation Language): **INSERT/ UPDATE/ DELETE** 資料定義語言(**DDL**, Data Definition Language): DDL不可以rollback **CREATE/ ALTER/ DROP/ RENAME/ TRUNCATE**(截斷:用於清空table裡的資料) 資料控制語言(**DCL**, Data Control Language): **GRANT**(授予權限)/ **REVOKE**(移除權限) ## DDL ### 修改欄位 ref: https://www.1keydata.com/tw/sql/sql-alter-table.html ## CRUD (DQL,DML) ### 查詢資料select ```sql SELECT 欄位1,欄位2 FROM 表格 WHERE 條件; ``` * AND運算子,左右**兩邊都必須滿足**才會被查到 * OR運算子,左右**有一邊滿足**就會被查到 ```sql SELECT 欄位1,欄位2 FROM 表格 WHERE 條件1 [AND/OR] 條件2; ``` * 也可讓**括號內的條件先判斷**做更複雜的查詢 ```sql SELECT 欄位1,欄位2 FROM 表格 WHERE 條件1 AND (條件2 OR 條件3); ``` **ORDER BY**排序 ```sql SELECT * FROM 表格 [WHERE 條件] ORDER BY 欄位1 [ASC/DESC],欄位2 [ASC/DESC]; --語法糖:也可以寫成ORDER BY 1,2,意思相同 ``` **DISTINCT** 排除重複 ```sql SELECT DISTINCT 欄位1,欄位2 FROM 表格; ``` **(NOT) IN** 拿**特定值**(以外)資料 ```sql SELECT * FROM 表格 WHERE 欄位 (NOT) IN ('xx','yy'); ``` **(NOT) BETWEEN** 拿**特定區間**(以外)資料 ```sql SELECT * FROM 表格 WHERE 欄位 (NOT) BETWEEN 1 AND 100; ``` **(NOT) LIKE** 搜尋**相似條件**(以外)資料 ```sql SELECT * FROM 表格 WHERE 欄位 (NOT) LIKE '%大%'; ``` * **Wildcard萬用字元**: 與**LIKE**搭配使用,可用於替代空出來的模糊字元以用於搜尋 **%**: 代替0~n個任意字元 **_**: 代替1個字元 **[字元]**: 代替1個指定字元 **[!字元]** 或 **[^字元]**: 代替1個指定排除字元 **AS**把欄位或表格**重新命名** ```sql SELECT 欄位1 AS 新名1, 欄位2 AS 新名2 FROM 表格 AS 新表格名; ``` **SubQuery子查詢** 將括弧內查詢子句視為一個暫時的table再做查詢 ```sql SELECT count(*) FROM ( SELECT * FROM tableA JOIN tableB...略) AS resultTable ``` **資料庫查詢分頁** ```sql SELECT ROW_NUMBER() OVER (ORDER BY 欄位1 ASC) AS row_num, --放在最前面作為索引 * FROM table_a WHERE 1=1 ORDER BY 欄位1 ASC OFFSET 0 * 5 rows fetch next 5 ROWS ONLY; ``` ### 新增資料insert ```sql INSERT INTO 表格 VALUES (欄位1值,欄位2值); --每個值都要依序寫 INSERT INTO 表格 (欄位1,欄位2) VALUES (欄位1值,欄位2值); --可以只寫特定幾個欄位 ``` 一次新增多筆 INSERT INTO VALUES ```sql INSERT INTO 表格 VALUES (欄位1值,欄位2值), (欄位1值,欄位2值), (欄位1值,欄位2值); --每個值都要依序寫 ``` 從其他表取得資料新增 INSERT INTO SELECT ```sql INSERT INTO 表格 (欄位1,欄位2) SELECT 其他表格欄位1,其他表格欄位2 FROM 其他表格; ``` 將查到的資料建成一個新表格 SELECT INTO(MySQL不支援) ```sql SELECT 欄位1,欄位2 INTO 新表格 FROM 現有表格; SELECT 欄位1,欄位2 INTO 新表格 FROM 現有表格 WHERE 0=1; --只複製表格結構 ``` ### 更新資料update ```sql UPDATE 表格 SET 欄位1=欄位1值,欄位2=欄位2值 WHERE 條件; UPDATE table1 alias SET (欄位1, 欄位2 ) = ( SELECT 欄位1, 欄位2 FROM table2 WHERE table2.column_name = alias.column_name) [WHERE column_name = VALUE] ``` ### 刪除資料delete ```sql DELETE FROM 表格 WHERE 條件; ``` ### 清除資料表truncate ```sql TRUNCATE TABLE XXXtable; ``` ## JOIN JOIN基本語法: ```sql SELECT a.*, b.* from table_a a join table_b b on a.join欄位 = b.join欄位; ``` 不同種類的JOIN: **(INNER) JOIN**: 內部連結,只有**左右表都符合**的結果才會出現 **LEFT JOIN**: 左外部連結,只要**左表**(寫在前join的表)有的就會出現在結果 **RIGHT JOIN**: 右外部連結,只要**右表**(寫在後被join的表)有的就會出現在結果 **FULL JOIN**: 全部外部連結,**無論有無符合條件**結果均會出現 **CROSS JOIN**: 交叉連結,兩表結合時,**不指定條件**,直接將兩者的所有可能排列出來 ```sql SELECT a.*, b.* from table_a a cross join table_b b ``` **NATURAL JOIN**: 自然連結,有NATURAL JOIN、NATURAL LEFT JOIN、NATURAL RIGHT JOIN,簡單的來說就是會將兩表**同名欄位連結在一起的INNER JOIN** **UNION**: 將兩個(以上) SQL 查詢的結果合併起來(**聯集**),兩結果必須有同樣的欄位/型別/順序,結果資料列只會返回不同值如DISTINCT ```sql SELECT 欄位1,欄位2... FROM table_a UNION SELECT 欄位1,欄位2... FROM table_b; ``` **UNION ALL**: 與UNION接近,只差在**不論是否重複皆返回** **INTERSECT**: **交集**,為前語句與後語句有**重疊**的部分 ```sql SELECT 欄位1,欄位2... FROM table_a INTERSECT SELECT 欄位1,欄位2... FROM table_b; ``` **EXCEPT**(ORACLE使用**MINUS**): **差集**,為(前語句結果) - (後語句結果)部分 ```sql SELECT 欄位1,欄位2... FROM table_a EXCEPT SELECT 欄位1,欄位2... FROM table_b; ``` ## Constraint Constraint是用於限制欄位那些資料可以被存入資料表中,主要是建立資料表時的指定條件或是之後可以ALTER TABLE修改: ### NOT NULL 限制欄位不能接受空值,使用方式為直接在欄位後加NOT NULL: ```sql CREATE TABLE XXX ( ID INT NOT NULL, NAME VARCHAR(50) NOT NULL) ``` ### UNIQUE 限制欄位必須唯一,使用方式為直接在欄位後加UNIQUE: ```sql CREATE TABLE XXX ( ID INT NOT NULL UNIQUE, NAME VARCHAR(50) NOT NULL ) ``` 或最後寫UNIQUE(欄位) ```sql CREATE TABLE XXX ( ID INT NOT NULL, NAME VARCHAR(50) NOT NULL, UNIQUE(ID) ); ``` 如果要綁定**複合唯一鍵**(只要不是全欄位重複皆可)可以寫成: CONSTRAINT 唯一鍵名稱 UNIQUE(欄位1,欄位2) ```sql CREATE TABLE XXX ( ID INT NOT NULL, NAME VARCHAR(50) NOT NULL, CONSTRAINT U_XXX_ID UNIQUE (ID,NAME) ); ``` ### PRIMARY KEY 限制欄位為**主鍵**,使用方式與UNIQUE接近,直接在欄位後加PRIMARY KEY ```sql CREATE TABLE XXX ( ID INT NOT NULL PRIMARY KEY, NAME VARCHAR(50) NOT NULL ) ``` 或最後寫PRIMARY KEY(欄位) ```sql CREATE TABLE XXX ( ID INT NOT NULL, NAME VARCHAR(50) NOT NULL, PRIMARY KEY(ID) ); ``` 綁定**複合主鍵**時,可以CONSTRAINT 主鍵名稱 PRIMARY KEY(欄位1,欄位2) ```sql CREATE TABLE XXX ( ID INT NOT NULL, NAME VARCHAR(50) NOT NULL, CONSTRAINT PK_XXX_ID PRIMARY KEY (ID,NAME) ); ``` ### FOREIGN KEY 限制欄位為外來鍵 ```sql CREATE TABLE XXX ( ID INT NOT NULL, NAME VARCHAR(50) NOT NULL, PRIMARY KEY (ID), FOREIGN KEY (C_ID) REFERENCES CUSTOMERS(C_ID) ); ``` 或是CONSTRAINT 外來鍵名 KEY(外來鍵欄位1) REFERENCES 參考table(參考欄位1) ```sql CREATE TABLE XXX ( ID INT NOT NULL PRIMARY KEY, NAME VARCHAR(50) NOT NULL, CONSTRAINT FK_XXX_ID FOREIGN KEY (C_ID) REFERENCES CUSTOMERS(C_ID) ); ``` ### CHECK(MySQL使用無效) 用來限制欄位中可以放入的值,寫法為CHECK(欄位名 限制條件) ```sql CREATE TABLE XXX ( ID INT NOT NULL, NAME VARCHAR(50) NOT NULL CHECK (NAME!='XXX'), PRIMARY KEY (ID), ); ``` 多欄位檢查時可以這樣寫: ```sql CREATE TABLE XXX ( ID INT NOT NULL PRIMARY KEY, NAME VARCHAR(50) NOT NULL, CPRIMARY KEY (ID), CONSTRAINT CHK_XXX CHECK (NAME!='XXX') ); ``` ### DEFAULT 用來指定如INSERT時沒有填入該欄位值,則會使用該預設值,直接在欄位後方DEFAULT '預設值' 即可 ```sql CREATE TABLE XXX ( ID INT NOT NULL, NAME VARCHAR(50) DEFAULT 'UNKNOWN', PRIMARY KEY (ID), ); ``` ## 常用函式(ORACLE) ```sql NVL(欄位,如果欄位為null時的值) DECODE(欄位,如欄位為值1,轉換值1,如欄位為值2,轉換值2,...,預設值) REGEXP_LIKE(欄位,'正則表示式字串') TO_DATE('待轉換字串','轉換格式字串') TO_CHAR(待轉換日期,轉換格式字串) TO_CHAR(SYSDATE,'yyyy-MM-dd') --快速轉化現在日期為指定格式字串 TO_DATE(TO_CHAR(sysdate,'yyyy-MM-dd'),'yyyy-MM-dd') --現在日期轉化特定格式日期 TO_DATE('2020-05-30', 'YYYY-MM-dd') --指定日期字串轉換成DATE to_date('2020-01-01 13:14:20', 'yyyy-MM-dd HH24:mi:ss'); --指定日期+時間轉換 REPLACE( 欄位, '欲替代字串', '轉換字串') LENGTH(欄位) --計算欄位字串長度 TRUNC(date)--直接截到剩日期 yyyy-MM-dd TRUNC(date[,fmt]) --用來截掉日期,fmt 日期格式,該日期將由指定的元素格式所截去。 TRUNC(number[,decimals]) --用來截掉數字,類似ROUND,不過此函示為無條件捨去 SELECT 序列名稱.nextval FROM DUAL; --查詢序列的下一個號碼,呼叫後會自己加上去 ``` ### 對每組colA取MIN/MAX (就不會只有取到整張tableA的MIN/MAX) ```sql select MIN(colPK), colA from tableA group by colA ``` ### 欄位colA去除重複,並依照date_column排序取最新 ```sql SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY colA ORDER BY date_column DESC) AS rn FROM your_table ) ranked WHERE rn = 1; ``` ### 分頁 ```sql SELECT ROW_NUMBER() OVER (ORDER BY date_column ASC) AS row_num,* FROM your_table WHERE 1=1 ORDER BY date_column ASC OFFSET 0 * 5 rows fetch next 5 ROWS ONLY; -- ``` ### 將table暫存於後續使用 ```sql WITH tempTable AS ( select ... ), tempTable2 AS ( select ... ) select ... --後續的select子句,可以從tempTable tempTable2取值; ``` ### 階層 (ORACLE) 接在語法最後,可以用來查出某資料轄下的所有資料 ```sql START WITH ParentNo= 'ROOT' --假設上面的欄位ParentNo名稱為ROOT CONNECT BY PRIOR PartNo = ParentNo --PartNo連結ParentNo串出階層樹 ``` ## SQL調校 1. 確認清除整張表資料時**TRUNCATE**替代DELETE 2. 速度: count **(欄位)** < count **(*)** < count **(1)** 3. 儘量用 **union** 代替 **or**,如: ```sql select * from 表 where id = 1 or id = 3; ``` 可改為 ```sql select * from 表 where id = 1 UNION select * from 表 where id = 3; ``` 4. 如果有預設判斷值盡量不使用**IS NULL**, 如果要判斷IS NOT NULL可以用**where LENGTH(欄位) > 0** 5. where 條件中表示式、函式操作移動到**等號右側**(**不要直接對欄位運算**) ```sql --會全表掃描 SELECT * FROM T WHERE score/10 = 9 --走索引 SELECT * FROM T WHERE score = 10*9 ``` 6. 盡量不使用 **<>** 或 **!=**,或**NOT**等否定查詢 7. in / exists ,如**可使用exist則不用join** 如兩表大小差不多: 兩者皆可 子查詢表**大**: 用**exist** 子查詢表**小**: 用**in** *注意:exist不用加欄位,主要是在括弧裡串原表的id及條件,in則可直接用欄位條件 ```sql -- 不走索引 SELECT * FROM 表 WHERE category IN ('A','B'); -- 走索引 SELECT t1.* FROM 表 t1 WHERE exists (SELECT * FROM 表 t2 where t2.category IN ('A','B') and t1.id = t2.id); --如只是要另一個表的交集,但沒有需要其欄位值可以用以下 SELECT t1.* FROM 表 t1 WHERE exists (SELECT 1 FROM 表 t2 where t2.category IN ('A','B') and t1.id = t2.id); ``` 8. 用>=取代>,<=取代<,可以直接讓DB從該"="的地方開始找 9. 資料重複項多: distinct效率高 資料唯一項多: group by效率高。 ```sql select distinct 欄位a,欄位b,欄位c from 表 ``` 可改為 ```sql select 欄位a,欄位b,欄位c from 表 group by 欄位a,欄位b,欄位c ``` 10. 沒有要排除重複時,可以用**UNION ALL**取代UNION 11. 子查詢盡量不要直接使用IN,可改為用一個**自定的表**取代 ```sql SELECT * FROM 表1 WHERE 欄位a IN (SELECT 欄位a FROM 表2) ``` 可改為 ```sql SELECT * FROM 表1, (SELECT colA FROM 表2) as tempTable WHERE 表2.欄位a = 表1.欄位a; ``` 12. **避免使用型別不同的值**來比較,假設salary欄位為浮點數,但比較時用整數 ```sql --應使用60000.00 select * from employee where salary>60000 ``` 13. 需增加**虛擬欄位**時(ex. '10' as catory ),務必不要在join table或去除重複時用,可等join完再補,否則會拖垮速度 14. 能直接insert就不要update,如要update就一次update完(where條件指定好) 15. 能**insert into select**就不要cursor繞迴圈(PL/SQL)