掌握 WHERE 子句的邏輯構建、常用函數的正確調用、聚合後過濾(HAVING)與 WHERE 的區別,是編寫高性能、正確語義 SQL 的關鍵。本文結合實戰案例系統講解。
本質:將表視為行集合,WHERE 後的表達式對每行求值,結果為 TRUE 的行進入結果集。
-- 等價思維:列 = 變量,WHERE = if(exp)
SELECT * FROM goods WHERE 1; -- 恆真,返回全表
SELECT * FROM goods WHERE category_id = 1 AND price > 100;
列可直接參與算術運算,結果可作為衍生列使用別名(不可在同層 WHERE 中使用別名)。
SELECT goods_id, goods_name,
(market_price - shop_price) AS discount
FROM goods
WHERE (market_price - shop_price) > 50; -- 必須重複表達式或用子查詢/CTE
正確復用別名:
SELECT * FROM (
SELECT goods_id, goods_name, (market_price - shop_price) AS discount
FROM goods
) t WHERE discount > 50;
| 符號 | 含義 | 示例 | 匹配示例 |
|---|---|---|---|
% | 任意長度字符(含 0) | LIKE 'abc%' | abc, abcd, abcdef |
_ | 單個任意字符 | LIKE 'ab_' | abc, abd, ab1 |
[] | 字符集合(需 REGEXP) | REGEXP '^ab[cd]' | abc, abd |
性能警示:LIKE '%kw%' 導致全表掃描,大表必須用全文索引或 ElasticSearch。
SELECT MAX(price), MIN(price), SUM(amount), AVG(score), COUNT(*)
FROM orders
GROUP BY category_id;
COUNT 細節:COUNT(*) 計行數(含 NULL)、COUNT(col) 計非 NULL 個數、COUNT(DISTINCT col) 計去重非 NULL 個數。
CONCAT('a','b') -- 'ab'
SUBSTRING('hello', 2, 3) -- 'ell'
TRIM(' abc ') -- 'abc'
UPPER/LOWER -- 大小寫轉換
REPLACE('a_b_c', '_', '-')-- 'a-b-c'
LENGTH vs CHAR_LENGTH -- 字節長度 vs 字符長度(多字節字符集區分)
NOW() -- 當前日期時間
CURDATE() -- 當前日期
DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s')
DATEDIFF('2024-12-31', '2024-01-01') -- 365
DATE_ADD(NOW(), INTERVAL 1 DAY)
TIMESTAMPDIFF(SECOND, '2024-01-01 00:00:00', NOW())
IF(condition, true_val, false_val)
IFNULL(expr, default_val) -- expr 為 NULL 返回 default
COALESCE(val1, val2, ...) -- 返回第一個非 NULL
CASE WHEN cond1 THEN v1 WHEN cond2 THEN v2 ELSE v3 END
NULLIF(expr1, expr2) -- 相等返回 NULL
ROUND(3.14159, 2) -- 3.14
CEIL/FLOOR/RAND()
MD5('pass'), SHA2('pass', 256)
VERSION(), CONNECTION_ID(), DATABASE(), USER()
-- 基礎分組
SELECT cat_id, SUM(goods_number) AS total_stock
FROM goods
GROUP BY cat_id;
-- 多維分組
SELECT cat_id, brand_id, COUNT(*) AS sku_cnt
FROM goods
GROUP BY cat_id, brand_id
WITH ROLLUP; -- 顯示小計行(cat_id 為 NULL 表示總計)
| 維度 | WHERE | HAVING |
|---|---|---|
| 作用階段 | 分組前,對原始行過濾 | 分組後,對聚合結果過濾 |
| 可用列 | 表列、衍生列(不可用聚合函數) | 分組列、聚合函數、衍生列 |
| 索引利用 | 可用索引 | 不可用索引(已聚合) |
| 典型場景 | 業務條件過濾 | 聚合指標閾值過濾 |
-- WHERE:過濾原始訂單
SELECT user_id, COUNT(*) AS order_cnt
FROM orders
WHERE status = 'paid' -- 原始行過濾
GROUP BY user_id;
-- HAVING:過濾聚合後的用戶
SELECT user_id, COUNT(*) AS order_cnt
FROM orders
WHERE status = 'paid'
GROUP BY user_id
HAVING COUNT(*) > 10; -- 聚合結果過濾
-- 組合:WHERE 先縮小數據集,HAVING 再篩選聚合指標
SELECT cat_id, AVG(price) AS avg_price
FROM goods
WHERE is_on_sale = 1 -- 利用索引快速過濾
GROUP BY cat_id
HAVING AVG(price) > 100; -- 聚合指標篩選
SELECT
g.goods_id, g.goods_name, g.shop_price,
(g.market_price - g.shop_price) AS discount,
c.cat_name, b.brand_name
FROM goods g
JOIN category c ON g.cat_id = c.cat_id
JOIN brand b ON g.brand_id = b.brand_id
WHERE g.is_on_sale = 1
AND g.is_delete = 0
AND (g.goods_name LIKE '%手機%' OR g.keywords LIKE '%手機%')
AND g.shop_price BETWEEN 1000 AND 5000
AND g.cat_id IN (SELECT cat_id FROM category WHERE parent_id = 3)
GROUP BY g.goods_id
HAVING COUNT(DISTINCT g.attr_value_id) >= 2 -- 至少有 2 個規格屬性
ORDER BY g.sort_order ASC, g.goods_id DESC
LIMIT 20 OFFSET 0;
WHERE COUNT(*) > 10 報錯,必須移至 HAVING。VARCHAR 列 = 數字,統一用字符串字面值。NOT IN 含 NULL 陷阱:WHERE col NOT IN (1,2,NULL) 永為 FALSE,改用 NOT EXISTS。OR 條件未全覆蓋索引導致全表掃描:拆分為 UNION ALL 或建組合索引。