SELECT 是 SQL 中最核心、變化最豐富、也是效能差異最大的指令。從簡單的單表查詢到複雜的多表連接、子查詢、視圖、CTE,掌握連接類型語意、WHERE/HAVING 過濾邏輯、GROUP BY 聚合規則、ORDER BY/LIMIT 分頁優化,是開發高效能應用的關鍵。本文系統整理 MySQL 支援的各類連接操作、欄位限定規則、別名應用、實戰解決方案模式,並以簡潔範例表格說明每種連接的資料集特徵。
SELECT [DISTINCT] select_expr, ...
FROM table_references
[WHERE where_condition]
[GROUP BY {col_name | expr | position} [ASC|DESC], ...]
[HAVING where_condition]
[ORDER BY {col_name | expr | position} [ASC|DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
MySQL 特有:FROM 子句可省略(純表達式求值),LIMIT 支援 OFFSET 語法。
以兩張範例表示範:
t1: id val
1 a
2 b
t2: id val
1 x
3 y
SELECT * FROM t1;
結果:t1 所有列。
SELECT * FROM t1, t2;
-- 或
SELECT * FROM t1 CROSS JOIN t2;
-- 或
SELECT * FROM t1 INNER JOIN t2; -- 無 ON 條件等同 CROSS JOIN
結果:4 列(2×2),所有組合。極度危險,大表會產生爆炸性列數,必須配合 WHERE 過濾。
SELECT * FROM t1, t2 WHERE t1.id = t2.id;
-- 标准语法
SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id;
SELECT * FROM t1 JOIN t2 ON t1.id = t2.id;
結果:僅 id=1 符合,1 列。INNER JOIN、JOIN、CROSS JOIN 在有 ON 條件時語意相同。
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id;
-- 同義詞
SELECT * FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.id;
-- ODBC 語法
SELECT * FROM {OJ t1 LEFT OUTER JOIN t2 ON t1.id = t2.id};
結果:t1 所有列(2 列),t2 無匹配處顯示 NULL。
| t1.id | t1.val | t2.id | t2.val |
|---|---|---|---|
| 1 | a | 1 | x |
| 2 | b | NULL | NULL |
MySQL 支援 RIGHT JOIN,語意與 LEFT JOIN 互換表順序。建議統一用 LEFT JOIN,閱讀習慣一致。
SELECT * FROM t1 NATURAL LEFT JOIN t2;
自動匹配同名欄位,不建議生產使用(Schema 變更易破壞)。
SELECT STRAIGHT_JOIN * FROM t1, t2 WHERE t1.id = t2.id;
-- 或 FROM 子句
SELECT * FROM t1 STRAIGHT_JOIN t2 ON t1.id = t2.id;
強制按 FROM 子句順序連接,繞過優化器重排。慎用,需配合 EXPLAIN 驗證確有收益。
tbl.col 或 alias.col-- 表別名
SELECT a.id, a.val, b.val AS b_val
FROM t1 AS a
LEFT JOIN t2 AS b ON a.id = b.id;
-- 自我連接(階層/遞迴結構)
SELECT e.name AS employee, m.name AS manager
FROM emp AS e
LEFT JOIN emp AS m ON e.manager_id = m.emp_id;
| 特性 | WHERE | HAVING |
|---|---|---|
| 執行階段 | 分組前、連接後 | 分組後 |
| 可用聚合函數 | 否 | 是 |
| 可用別名 | 否(標準 SQL) | 是 |
| 索引利用 | 是 | 否 |
實戰原則:能用 WHERE 過濾的盡量用 WHERE,HAVING 僅用於聚合條件(如 HAVING COUNT(*) > 5)。
MySQL 5.7+ 預設啟用 ONLY_FULL_GROUP_BY,要求 SELECT 清單中非聚合欄位必須出現在 GROUP BY 中。
-- 錯誤(嚴格模式)
SELECT customer_id, COUNT(*), MAX(amount) FROM orders GROUP BY customer_id;
-- 正確
SELECT customer_id, COUNT(*), MAX(amount) FROM orders GROUP BY customer_id, MAX(amount);
-- 或使用 ANY_VALUE()
SELECT customer_id, COUNT(*), ANY_VALUE(MAX(amount)) FROM orders GROUP BY customer_id;
SELECT t1.* FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE t2.id IS NULL;
WITH ranked AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC) AS rn
FROM scores
)
SELECT * FROM ranked WHERE rn <= 3;
舊版本:GROUP_CONCAT + 應用層分割、或相關子查詢(效能差)。
-- 低效:OFFSET 掃描大量列
SELECT * FROM orders ORDER BY created_at DESC LIMIT 100000, 10;
-- 高效:游標式分頁(需唯一排序鍵)
SELECT * FROM orders
WHERE created_at < '2024-01-15 10:00:00' -- 上一頁最後一筆時間
ORDER BY created_at DESC LIMIT 10;
INSERT INTO stats (date, counter) VALUES ('2024-01-15', 1)
ON DUPLICATE KEY UPDATE counter = counter + 1;
-- 刪除訂單及其明細
DELETE o, d FROM orders o
JOIN order_details d ON o.id = d.order_id
WHERE o.created_at < '2023-01-01';
-- 更新主表統計欄位
UPDATE orders o
JOIN (
SELECT order_id, SUM(amount) AS total
FROM order_details GROUP BY order_id
) d ON o.id = d.order_id
SET o.total_amount = d.total;
| 場景 | 推薦 | 理由 |
|---|---|---|
| 子查詢回傳單值(標量) | 子查詢 | 可讀性佳,優化器常轉為連接 |
| IN/EXISTS 子查詢 | EXISTS / JOIN | EXISTS 半連接優化,IN 早期版本效能差 |
| FROM 子查詢(衍生表) | 避免 / CTE / 視圖 | 物化暫存表無索引,5.6+ 支援衍生表合併 |
| 相關子查詢 | 改寫為 JOIN | 逐列執行,效能極差 |
LIMIT 10:前 10 列LIMIT 10, 20:跳過 10 列,取 20 列(OFFSET 10 LIMIT 20)LIMIT 0, 10:等同 LIMIT 10ORDER BY 確保結果確定性OFFSET 掃描代價檢索資料的藝術在於用最精準的連接類型表達業務邏輯、用最早的 WHERE 過濾削減資料集、用最合適的索引支援存取路徑、用最確定的 ORDER BY 保證分頁穩定。建立「先寫 SQL 邏輯、再看 EXPLAIN、最後調型別/索引/寫法」的開發習慣,並掌握上述解決方案模式庫,可應對 90% 以上的查詢場景。MySQL 8.0 引入 CTE、窗口函數、側向連接,更將複雜查詢的表達力提升至新高度——升級版本、學習新語法,是持續提升檢索能力的必經之路。