SELECT e.name, d.name AS deptFROM employees eINNER JOIN departments d ON e.dept_id = d.id;
LEFT JOIN — 左表全留;右表配不到的欄位補 NULL(找「沒有對應資料的那些」時最常用)。
SELECT e.name, d.name AS deptFROM employees eLEFT JOIN departments d ON e.dept_id = d.id;-- 找出沒有任何訂單的員工SELECT e.nameFROM employees eLEFT JOIN orders o ON o.employee_id = e.idWHERE o.id IS NULL;
RIGHT JOIN — 反過來,右表全留;通常改寫成 LEFT JOIN 比較好讀。
CROSS JOIN — 笛卡兒積,左表每一列配右表每一列(列數相乘,小心爆量)。
SELECT e.name, d.name FROM employees e CROSS JOIN departments d;
SELF JOIN — 同一張表 join 自己,用別名區分(例如查每個人的主管)。
SELECT e.name AS staff, m.name AS managerFROM employees eLEFT JOIN employees m ON e.manager_id = m.id;
3. 條件與運算子
AND — 兩個條件都要成立。
SELECT name FROM employees WHERE salary > 50000 AND dept_id = 3;
OR — 任一條件成立即可(和 AND 混用時用括號講清楚優先序)。
SELECT name FROM employees WHERE dept_id = 1 OR dept_id = 2;
NOT(+) — 把條件反過來。
SELECT name FROM employees WHERE NOT dept_id = 3;
BETWEEN — 落在區間內,含兩端(等價於 >= AND <=)。
SELECT name FROM employees WHERE salary BETWEEN 40000 AND 60000;
IN — 值落在一組候選值裡;候選值可以是寫死的清單,也可以是子查詢。
SELECT name FROM employees WHERE dept_id IN (1, 2, 5);-- 子查詢版:在台北的部門的員工SELECT name FROM employeesWHERE dept_id IN (SELECT id FROM departments WHERE name LIKE '台北%');
EXISTS(+) — 子查詢「有沒有任何一列」就回 true;比 IN 適合大量資料,因為找到一筆就能停。
SELECT e.name FROM employees eWHERE EXISTS (SELECT 1 FROM orders o WHERE o.employee_id = e.id);
IS NULL — 判斷是否為 NULL。不能寫 = NULL,因為 NULL 代表「未知」,跟任何值比較(含它自己)結果都是未知而不是 true。
SELECT name FROM employees WHERE manager_id IS NULL; -- 沒有主管的人SELECT name FROM employees WHERE manager_id IS NOT NULL;
LIKE(+) — 字串模糊比對,% 代表任意長度、_ 代表單一字元。
SELECT name FROM employees WHERE name LIKE '王%';
CASE … WHEN … THEN … ELSE … END — SQL 裡的 if/else,依條件產生不同的值(原清單寫的 CASE THEN)。
SELECT name, CASE WHEN salary >= 80000 THEN 'high' WHEN salary >= 50000 THEN 'mid' ELSE 'low' END AS salary_bandFROM employees;
常用技巧:塞在聚合函式裡做條件式計數。
SELECT dept_id, COUNT(*) AS total, SUM(CASE WHEN salary > 60000 THEN 1 ELSE 0 END) AS high_paidFROM employees GROUP BY dept_id;
COALESCE / IFNULL(+) — 取第一個不是 NULL 的值,用來給預設值。
SELECT name, COALESCE(manager_id, 0) AS manager_id FROM employees;
4. 聚合函式(+)
GROUP BY 的另一半,沒有它分組沒有意義。
COUNT — 計數;COUNT(*) 算列數,COUNT(欄位) 會跳過 NULL。
SELECT COUNT(*) AS rows_all, COUNT(manager_id) AS has_manager FROM employees;
SUM / AVG — 總和/平均(同樣忽略 NULL)。
SELECT dept_id, SUM(salary) AS total, AVG(salary) AS avg FROM employees GROUP BY dept_id;
MIN / MAX — 最小/最大值。
SELECT MIN(hired_at) AS first_hire, MAX(salary) AS top_salary FROM employees;
GROUP_CONCAT — 把群組內的值串成一個字串(MySQL 專屬)。
SELECT dept_id, GROUP_CONCAT(name ORDER BY name SEPARATOR ', ') AS membersFROM employees GROUP BY dept_id;
聚合函式會把多列收成一列;window function 保留每一列,只是額外算一個「看得到同組其他列」的值。
OVER — 宣告這是視窗函式,括號裡定義它看得到的範圍。
SELECT name, salary, AVG(salary) OVER () AS company_avg FROM employees;
PARTITION BY — 把視窗切成群組(概念像 GROUP BY,但不會把列合併掉)。
SELECT name, dept_id, salary, AVG(salary) OVER (PARTITION BY dept_id) AS dept_avgFROM employees;
ROW_NUMBER — 在視窗內依排序給每列一個不重複的序號(1, 2, 3…)。
-- 每個部門薪水最高的那個人SELECT * FROM ( SELECT name, dept_id, salary, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn FROM employees) tWHERE rn = 1;
SELECT name, salary, RANK() OVER (ORDER BY salary DESC) AS rk, DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rkFROM employees;
LAG / LEAD(+) — 取同一視窗內前一列/後一列的值,算環比、找差異用。
SELECT created_at, amount, amount - LAG(amount) OVER (ORDER BY created_at) AS diff_from_prevFROM orders;
ROWS BETWEEN(+) — 指定視窗的框(frame),做移動平均、累計加總。
SELECT created_at, amount, SUM(amount) OVER (ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_totalFROM orders;
7. CTE
WITH — 先給子查詢取一個名字,再在主查詢裡當表用;把巢狀子查詢攤平成由上而下可讀的步驟。
WITH dept_avg AS ( SELECT dept_id, AVG(salary) AS avg_salary FROM employees GROUP BY dept_id)SELECT e.name, e.salary, d.avg_salaryFROM employees eJOIN dept_avg d ON e.dept_id = d.dept_idWHERE e.salary > d.avg_salary;
WITH RECURSIVE(+) — 讓 CTE 引用自己,用來展開樹狀/階層資料(組織圖、分類目錄)。
WITH RECURSIVE chain AS ( SELECT id, name, manager_id, 1 AS lvl FROM employees WHERE manager_id IS NULL UNION ALL SELECT e.id, e.name, e.manager_id, c.lvl + 1 FROM employees e JOIN chain c ON e.manager_id = c.id)SELECT * FROM chain ORDER BY lvl;
8. Temporary Tables / 建表
CREATE TABLE — 建立一張表並定義欄位與型別。
CREATE TABLE departments ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL);
TEMPORARY — 建成暫存表:只有當前連線看得到,連線關掉就自動消失,適合放中間結果。
CREATE TEMPORARY TABLE tmp_high_paid ASSELECT * FROM employees WHERE salary > 80000;
CREATE TABLE … AS SELECT(+) — 直接用查詢結果建表(上面那個寫法就是;只複製資料與欄位,不複製索引)。
(+)ALTER TABLE / DROP TABLE / TRUNCATE — 改結構/刪整張表/清空資料但保留表。