Learning Objectives
- 用 SELECT 取出需要的欄位,用 WHERE 篩選資料列
- 用 ORDER BY 排序、用 LIMIT 取出部分結果,說明沒有 ORDER BY 時列的順序不保證
- 使用 DISTINCT、欄位別名與表格別名
- 用 COUNT 等聚合函式與 GROUP BY 分組統計,分辨 WHERE 與 HAVING
- 說出 SELECT 各子句實際的執行順序:FROM、WHERE、GROUP BY、HAVING、SELECT、ORDER BY、LIMIT
本課使用的資料庫
本課沿用第 04、05 課建立的 employee 資料庫,記錄一家顧問公司的部門、員工、技能、客戶與派工:
| 表格 | 內容 |
|---|---|
department |
部門:departmentID、name |
employee |
員工:employeeID、name、job、departmentID(外鍵,指向部門) |
employeeSkills |
員工技能:employeeID、skill,一位員工可以有多項技能 |
client |
客戶:clientID、name、address、contactPerson、contactNumber |
assignment |
派工:哪位員工在哪一天替哪個客戶工作幾小時 |
下面的範例在執行前,資料庫裡已經有這些表格和資料。播放時,右邊會顯示資料庫裡的表格, 左邊顯示「這一步」的中間結果:一個 SELECT 會拆成好幾步,讓你看到每個子句做了什麼。
SELECT 與 WHERE
最簡單的查詢取出整張表格:
SELECT * FROM employee;
* 代表所有欄位。實務上建議只列出需要的欄位,傳回的資料比較少,表格之後增加欄位時,程式也不會受到影響:
SELECT name, job FROM employee;
WHERE 加上條件,只留下符合條件的資料列:
SELECT name, job
FROM employee
WHERE job = 'Programmer';
- 字串用單引號括起來。比較時不分大小寫:
'programmer'也會符合,這是 MySQL 8 預設的定序utf8mb4_0900_ai_ci的規則(ci 是 case-insensitive) - 比較運算子:
=、<>(或!=)、<、<=、>、>= - 多個條件用
AND、OR組合,NOT取相反;混合使用時加上括號,避免誤解運算順序 - 一個敘述可以寫成很多行,以分號
;結束
SELECT 與 WHERE 的執行過程
第二個查詢會分成三步:FROM 先把整張 employee 讀進來,WHERE 逐列檢查條件(畫刪除線的列被濾掉),最後 SELECT 只留下 name 和 job 兩個欄位。右邊表格裡淺藍色的列,是這個查詢讀到的資料。
ORDER BY 與 LIMIT
沒有 ORDER BY 時,資料列的順序是不保證的。 InnoDB 通常依主鍵的順序讀取,
但如果查詢只用到某個索引的欄位,MySQL 會改讀那個索引,順序就跟著改變。需要固定順序,一定要寫 ORDER BY:
SELECT * FROM employee
ORDER BY job ASC, name DESC; -- 先依 job 由小到大,job 相同時依 name 由大到小
ASC 是遞增(預設,可以省略),DESC 是遞減。字串依定序比較:不分大小寫,數字排在字母前面。
LIMIT 只取出部分結果,常用在分頁:
SELECT * FROM employeeSkills LIMIT 5; -- 前 5 列
SELECT * FROM employeeSkills LIMIT 5, 3; -- 跳過 5 列,再取 3 列
SELECT * FROM employeeSkills LIMIT 3 OFFSET 5; -- 和上一行相同
LIMIT 是在排序之後才取,所以「前 3 名」要先 ORDER BY 再 LIMIT 3。
排序之後再取出部分結果
第一個查詢觀察 ORDER BY 的兩層排序:DBA 排在 Programmer 前面,兩位 Programmer 再依姓名由大到小。第二個查詢的 LIMIT 那一步,畫刪除線的列是沒有被取出的:跳過前 2 列,取接下來的 3 列。
想一想:WHERE、ORDER BY 與 LIMIT
employee 有 4 位員工,其中 2 位的 job 是 Programmer。下面的查詢傳回幾列?
SELECT name FROM employee WHERE job = 'programmer';
要找出 employeeID 最大的兩位員工,哪一個寫法正確?
下面的查詢會傳回幾列?
SELECT * FROM employeeSkills LIMIT 6, 5;
(employeeSkills 共有 8 列)
DISTINCT 與別名
DISTINCT 去掉重複的結果列:
SELECT DISTINCT job FROM employee; -- 每種職稱只出現一次
SELECT COUNT(DISTINCT job) FROM employee; -- 有幾種不同的職稱
欄位別名用 AS 替結果欄位取名字,AS 可以省略;表格別名讓欄位名稱寫起來比較短,第 07 課的多表查詢會大量使用:
SELECT name AS employeeName FROM employee;
SELECT name employeeName FROM employee; -- 省略 AS
SELECT e.name FROM employee AS e; -- 表格別名
SELECT employee.name FROM employee; -- 完整寫出表格名稱
定義了表格別名之後,同一個查詢裡就要用別名,不能再寫原本的表格名稱。
聚合函式與 GROUP BY
聚合函式把很多列算成一個值:
| 函式 | 作用 |
|---|---|
COUNT(*) |
列數 |
COUNT(欄位) |
這個欄位不是 NULL 的列數 |
SUM(欄位)、AVG(欄位) |
加總、平均(忽略 NULL) |
MIN(欄位)、MAX(欄位) |
最小值、最大值 |
GROUP BY 先把資料列依某個欄位分組,聚合函式再對每一組計算一次:
SELECT departmentID, COUNT(*) AS n
FROM employee
GROUP BY departmentID; -- 每個部門有幾位員工
要篩選分組之後的結果,用 HAVING;WHERE 是在分組之前篩選資料列,所以 WHERE 裡不能用聚合函式:
SELECT job, COUNT(*) AS n
FROM employee
WHERE departmentID = 128 -- 分組前:只看部門 128 的員工
GROUP BY job
HAVING n >= 2; -- 分組後:只留下人數至少 2 的職稱
MySQL 8 預設開啟 ONLY_FULL_GROUP_BY:SELECT 清單裡沒有放進聚合函式的欄位,必須寫在 GROUP BY 裡。
例如 SELECT name, COUNT(*) FROM employee GROUP BY job; 會出現 ERROR 1055,因為同一個職稱有好幾位員工,MySQL 不知道要顯示哪一個 name。
分組統計的執行順序
GROUP BY 那一步,同一組的列左邊標著同一種顏色。第二個查詢依序經過 FROM、WHERE、GROUP BY、HAVING、SELECT、ORDER BY,注意 SELECT 是在 HAVING 之後才算出 n,但 MySQL 允許 HAVING 先使用這個別名。
想一想:WHERE、GROUP BY 與 HAVING
SELECT 的各個子句,實際執行的順序是哪一個?
下面的查詢會傳回什麼?
SELECT COUNT(DISTINCT job) FROM employee;
想找出「技能數量至少 3 項」的員工編號,條件應該寫在哪裡?
SELECT employeeID, COUNT(*) AS n FROM employeeSkills
GROUP BY employeeID
______ n >= 3;
動手寫:每位員工的技能數
請查詢 employeeSkills,列出每位員工的 employeeID 與技能數量(欄位名稱取為 skills),依技能數量由多到少排序,數量相同時依 employeeID 由小到大。
重點整理
| 子句 | 作用 | 執行順序 |
|---|---|---|
FROM |
資料從哪張表格來 | 1 |
WHERE |
篩選資料列(不能用聚合函式) | 2 |
GROUP BY |
分組 | 3 |
HAVING |
篩選分組後的結果 | 4 |
SELECT |
算出要顯示的欄位、DISTINCT 去重複 |
5 |
ORDER BY |
排序(可以使用 SELECT 的別名) | 6 |
LIMIT |
取出部分結果 | 7 |
- 沒有
ORDER BY時資料列的順序不保證 - 字串比較預設不分大小寫
- MySQL 8 的
ONLY_FULL_GROUP_BY:沒有聚合的欄位必須出現在 GROUP BY
下一課把多張表格連起來查詢:哪位員工在哪個部門、哪個客戶由誰負責。