SELECT [ALL|DISTINCT|DISTINCTROW]
{* | table.* | col1 [AS alias1], col2 [AS alias2], ...}
FROM table_name [AS alias]
[WHERE condition]
[GROUP BY col_list [HAVING condition]]
[ORDER BY col_list [ASC|DESC]]
[LIMIT offset, row_count];
SELECT *:全列(生產禁用,網絡/IO/解析開銷大)SELECT col1, col2:指定列,順序決定輸出順序SELECT col1 AS 別名, col2 別名:重命名,含特殊字符用反引號SELECT DISTINCT col1, col2:去重(多列組合去重)SELECT TOP n / LIMIT n:限制行數(MySQL 用 LIMIT)| 運算符類別 | 運算符 | 示例 | 說明 |
|---|---|---|---|
| 比較 | =, <>, != | age = 25 | 等於/不等於 |
| >, >=, <, <= | score >= 60 | 範圍比較 | |
| IS NULL / IS NOT NULL | deleted_at IS NULL | 空值判斷(= NULL 永假) | |
| BETWEEN ... AND ... | age BETWEEN 18 AND 60 | 閉區間 [18,60] | |
| IN (val1, val2...) | status IN (1,2,3) | 枚舉匹配 | |
| NOT IN (...) | id NOT IN (SELECT ...) | 反向枚舉(慎用子查詢) | |
| LIKE / NOT LIKE | name LIKE '張%' | % 任意長度,_ 單字符,[] 字符集 | |
| REGEXP / RLIKE | email REGEXP '^[^@]+@[^@]+\.[^@]+$' | 正則表達式匹配 | |
| 邏輯 | AND / && | age>18 AND status=1 | 優先級高於 OR |
| OR / || | role=1 OR role=2 | ||
| NOT / ! | NOT (status=0) | 取反 | |
| XOR | a=1 XOR b=1 | 異或 |
ORDER BY create_time DESC, id ASC -- 多鍵排序
LIMIT 10 OFFSET 20 -- 跳過 20 行取 10 行(第 3 頁,每頁 10 條)
LIMIT 20, 10 -- 等價寫法
深分頁優化:LIMIT 1000000, 10 極慢 → 改用延遲關聯或游標分頁:
SELECT * FROM t WHERE id > (SELECT id FROM t ORDER BY id LIMIT 1000000, 1) ORDER BY id LIMIT 10;
| 類型 | 關鍵字 | 結果集包含 | 典型場景 |
|---|---|---|---|
| 內連接 | INNER JOIN / JOIN | 雙方均匹配的行 | 主表關聯明細、維度表關聯 |
| 左外連接 | LEFT [OUTER] JOIN | 左表全量 + 右表匹配行(無匹配填 NULL) | 主表帶出可選屬性、報表統計 |
| 右外連接 | RIGHT [OUTER] JOIN | 右表全量 + 左表匹配行 | 少用,可改 LEFT JOIN 交換表順序 |
| 全外連接 | FULL [OUTER] JOIN | 雙方全量,無匹配填 NULL | MySQL 不支援,用 UNION 模擬 |
| 交叉連接 | CROSS JOIN | 笛卡爾積(A行數 × B行數) | 生成測試數據、全排列組合 |
-- 內連接(推薦寫法)
SELECT u.name, o.order_no, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE u.status = 1;
-- 左連接(帶條件注意:ON vs WHERE 差異)
SELECT u.name, o.order_no
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 1 -- 條件在 ON 中:先過濾右表再連接
WHERE u.city = '北京';
-- 多表連接
SELECT u.name, o.order_no, p.product_name, od.quantity
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_details od ON o.id = od.order_id
JOIN products p ON od.product_id = p.id;
ON vs WHERE 陷阱:LEFT JOIN 時,右表過濾條件放 ON 保留左表行,放 WHERE 會隱性轉為內連接。
SELECT id, name FROM users_2022 WHERE city='北京'
UNION
SELECT id, name FROM users_2023 WHERE city='北京'
ORDER BY name; -- 只能在最後 ORDER BY,按第一個 SELECT 列排序
UNION:去重(預設),性能較差UNION ALL:保留重複,**大數據合併首選**,無去重開銷
SELECT * FROM products
WHERE price > (SELECT AVG(price) FROM products);
SELECT * FROM users
WHERE (city, district) = (SELECT city, district FROM branches WHERE id=1);
SELECT d.dept_name, e.avg_salary
FROM departments d
JOIN (
SELECT dept_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY dept_id
) e ON d.id = e.dept_id;
-- 高性能:只要存在即返回,不掃描全表
SELECT * FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.amount > 1000
);
-- 等價 IN 但通常更優(特別是 NOT IN 有 NULL 陷阱)
SELECT * FROM users u
WHERE u.id IN (SELECT user_id FROM orders WHERE amount > 1000);
避坑:NOT IN (子查詢) 若子查詢結果含 NULL,整體返回空 → 改用 NOT EXISTS。
-- 排名
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk
DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS drk
-- 偏移
LAG(salary) OVER (PARTITION BY dept ORDER BY hire_date) AS prev_salary
LEAD(salary) OVER (PARTITION BY dept ORDER BY hire_date) AS next_salary
-- 聚合
SUM(salary) OVER (PARTITION BY dept) AS dept_total
AVG(salary) OVER (PARTITION BY dept ORDER BY hire_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_avg
type(ALL/index/range/ref/eq_ref/const)、rows、Extra(Using index/Using where/Using temporary/Using filesort)IS NULL、<>、LIKE '%...' 不走索引ANALYZE TABLE 定期更新,優化器依賴其決策
WITH daily_stats AS (
SELECT
DATE(create_time) AS stat_date,
COUNT(*) AS order_count,
SUM(amount) AS total_amount,
AVG(amount) AS avg_amount,
COUNT(DISTINCT user_id) AS buyer_count
FROM orders
WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
AND status IN (1,2,3) -- 已付款/已發貨/已完成
GROUP BY DATE(create_time)
),
user_rfm AS (
SELECT
user_id,
MAX(DATE(create_time)) AS last_order_date,
COUNT(*) AS frequency,
SUM(amount) AS monetary
FROM orders
WHERE status IN (2,3)
GROUP BY user_id
)
SELECT
d.stat_date,
d.order_count,
d.total_amount,
d.buyer_count,
COUNT(CASE WHEN u.monetary > 1000 THEN 1 END) AS high_value_buyers
FROM daily_stats d
LEFT JOIN user_rfm u ON d.stat_date = u.last_order_date
GROUP BY d.stat_date
ORDER BY d.stat_date DESC;