Learning Objectives
- 說明 NULL 和任何值比較的結果都是 NULL,改用 IS NULL 或 <=> 判斷
- 在需要區分大小寫時使用 COLLATE utf8mb4_bin 或 CAST(… AS BINARY)
- 用 IF、CASE、IFNULL、COALESCE 依條件產生不同的值
- 使用常用的字串函式,分辨 LENGTH(位元組)與 CHAR_LENGTH(字元)
- 說明 ROUND 對 DECIMAL 與 FLOAT 的差別,以及除法結果的小數位數
- 用日期函式計算日期與天數,並避開日期沒加引號的陷阱
在 SELECT 裡使用函式
MySQL 內建數百個函式和運算子,可以在查詢時直接處理資料:把姓名轉成大寫、算出派工距今幾天、 依職稱顯示不同的文字。資料留在資料庫裡處理,程式端就不必把所有資料取回來再逐筆計算。
試用函式時,SELECT 可以不寫 FROM,直接算出一個值:
SELECT 1 + 1, UPPER('mysql'), NOW();
在 mysql 用戶端輸入 HELP 函式名稱(例如 HELP LIKE、HELP DATE_ADD)可以查看說明,
完整清單在 MySQL 8.0 參考手冊的 Functions and Operators 一章。
本課的範例沿用 employee 資料庫。播放器裡的 NOW() 固定為 2026-09-01 09:00:00、伺服器時區是 UTC,
所以每次執行的結果相同;在真實的伺服器上執行,會得到當下的時間。
比較運算子與 NULL
比較運算子的結果是 1(真)、0(假)或 NULL(不知道):
| 運算子 | 意思 |
|---|---|
=、<>(或 !=) |
相等、不相等 |
<、<=、>、>= |
大小比較 |
n BETWEEN a AND b |
a <= n AND n <= b,包含兩端 |
n IN (a, b, c) |
n 是否等於清單裡的某一個值,清單也可以是子查詢 |
n IS NULL、n IS NOT NULL、ISNULL(n) |
是否為 NULL |
<=> |
可以比較 NULL 的相等:NULL <=> NULL 是 1 |
NULL 代表「不知道的值」,所以任何值和 NULL 比較,答案都是「不知道」:NULL = NULL 的結果是 NULL,不是 1。
WHERE 只留下條件為 1 的列,WHERE x = NULL 因此永遠找不到資料,要寫 WHERE x IS NULL。
邏輯運算子 AND、OR、NOT、XOR 也遵守同樣的規則(三值邏輯):
0 AND NULL是 0:只要有一邊是假,不必知道另一邊1 AND NULL是 NULL:要看另一邊,但另一邊不知道1 OR NULL是 1、0 OR NULL是 NULL、NOT NULL是 NULL
課程投影片的 &&、||、! 是 MySQL 自己的寫法,MySQL 8.0 執行時會出現警告 1287(已棄用,將來會移除),請改寫成 AND、OR、NOT。
區分大小寫的比較
第 06 課提過,MySQL 8 預設的定序 utf8mb4_0900_ai_ci 比較字串時不分大小寫,所以 name = 'marketing' 也找得到 Marketing。
需要區分大小寫(例如比對代碼、密碼雜湊)時,舊教材的寫法是在字串前面加 BINARY:
SELECT * FROM department WHERE name = BINARY 'marketing';
這個寫法仍然有效,但從 MySQL 8.0.27 起會出現警告 1287:'BINARY expr' is deprecated。改用下面兩種寫法:
-- 指定區分大小寫的定序(_bin 依字元的編碼逐一比較)
SELECT * FROM department WHERE name = 'marketing' COLLATE utf8mb4_bin;
-- 把字串轉成位元組再比較
SELECT * FROM department WHERE CAST(name AS BINARY) = 'marketing';
在 WHERE 的欄位上套用函式或改定序,MySQL 可能無法使用這個欄位的索引;如果某個欄位經常需要區分大小寫,
建表時直接把欄位的定序設為 utf8mb4_bin 比較好。
NULL、三值邏輯與大小寫
前三個敘述沒有 FROM,直接算出結果。注意 3 NOT IN (1, 2, NULL) 的結果是 NULL:MySQL 無法確定 3 不等於那個 NULL。後面四個查詢比較 marketing 的大小寫:加上 BINARY 之後找不到資料,而且多了一個警告;COLLATE utf8mb4_bin 也區分大小寫,但沒有警告。
想一想:NULL 與比較
下面的敘述會傳回什麼?
SELECT NULL = NULL;
下面的敘述會傳回什麼?
SELECT 3 NOT IN (1, 2, NULL);
在 MySQL 8.0.44 上要找出名稱「完全等於」marketing(小寫)的部門,而且不出現棄用警告,應該怎麼寫?
IF、CASE、IFNULL 與 COALESCE
流程控制函式依條件產生不同的值:
| 函式 | 作用 |
|---|---|
IF(條件, 值1, 值2) |
條件為真時傳回值1,否則傳回值2 |
CASE WHEN 條件1 THEN 值1 WHEN 條件2 THEN 值2 ELSE 值3 END |
依序檢查條件,傳回第一個成立的值 |
CASE 欄位 WHEN 值a THEN … WHEN 值b THEN … ELSE … END |
欄位等於某個值時傳回對應的結果 |
IFNULL(x, 預設值) |
x 是 NULL 時改用預設值 |
COALESCE(a, b, c, …) |
傳回第一個不是 NULL 的值,是 SQL 標準的寫法 |
CASE 沒有 ELSE 而且條件都不成立時,結果是 NULL。
課程投影片的陷阱:日期沒加引號。 實習 Lab01 有這樣的查詢:
SELECT workdate, CASE
WHEN workdate < 2000-01-01 THEN 'archived'
WHEN workdate < 2003-01-01 THEN 'old'
ELSE 'current'
END
FROM assignment;
2000-01-01 沒有加引號,MySQL 把它當成算式 2000 − 1 − 1,也就是整數 1998;2003-01-01 則是 2001。
1998 不是合法的日期,MySQL 改把 workdate 當成數字 19990630 來比較,19990630 < 1998 永遠不成立,
所以每一列都是 current。在真實的 MySQL 8.0.44 上,派工日期是 1999、2001、2003 年的三列會得到:
+------------+---------+
| workdate | age |
+------------+---------+
| 1999-06-30 | current |
| 2001-11-05 | current |
| 2003-01-20 | current |
+------------+---------+
3 rows in set, 2 warnings (0.00 sec)
兩個警告是 1292 Incorrect date value: '1998' for column 'workdate',以及同樣訊息的 '2001',這是唯一的提示。
日期一律寫成字串 '2000-01-01'。下面的範例先新增兩筆派工,再示範正確的寫法。
依條件產生不同的值
第三個敘述把兩種寫法放在一起:沒加引號的 2000-01-01 是整數 1998,加了引號才是日期。第四個查詢用加了引號的日期,三筆派工才分成 archived、old、current。倒數第二個查詢用 IFNULL 把「沒有派工」的 NULL 換成 0。
字串函式與 LIKE
| 函式 | 範例 | 結果 |
|---|---|---|
CONCAT(a, b, …) |
CONCAT(name, ' (', job, ')') |
Ben Smith (DBA);任一參數是 NULL,結果就是 NULL |
LENGTH(s) |
LENGTH('資料庫') |
9:位元組數,utf8mb4 的中文字通常佔 3 個位元組 |
CHAR_LENGTH(s) |
CHAR_LENGTH('資料庫') |
3:字元數 |
SUBSTRING(s, 起點, 長度) |
SUBSTRING('Research', 5) |
arch:位置從 1 開始,省略長度就取到最後 |
LEFT(s, n)、RIGHT(s, n) |
LEFT('Research', 3) |
Res |
UPPER(s)、LOWER(s) |
UPPER('nora') |
NORA |
TRIM(s)、LTRIM、RTRIM |
TRIM(' hi ') |
hi:去掉前後的空白 |
REPLACE(s, 舊, 新) |
REPLACE('Telco Inc', 'Inc', 'Ltd') |
Telco Ltd |
STRCMP(a, b) |
STRCMP('cat', 'dog') |
-1:a 排在 b 前面;相同是 0,排在後面是 1 |
檢查中文姓名、標題長度時要用 CHAR_LENGTH;VARCHAR(30) 的 30 也是字元數。
STRCMP 依照定序比較,所以 STRCMP('Cat', 'cat') 是 0。
LIKE 做萬用字元比對:% 代表任意個字元(包括 0 個),_ 代表剛好一個字元。
投影片備註提到的 ? 在 MySQL 的 LIKE 裡沒有特殊意義,就是問號本身。
WHERE name LIKE '%research%' -- 包含 research(預設不分大小寫)
WHERE name LIKE '_o%' -- 第二個字是 o
WHERE code LIKE '100\%' -- 要比對 % 本身,前面加反斜線
LIKE '%…' 開頭就是萬用字元時,無法利用索引,資料量大時要注意(第 17 課查詢最佳化會再說明)。
字串函式
第二個敘述比較英文與中文的 LENGTH、CHAR_LENGTH:英文字一個字元一個位元組,中文字一個字元三個位元組。第五個查詢的 WHERE 那一步,觀察哪兩位員工符合 _o%(第二個字是 o)或 %h(最後一個字是 h)。
想一想:流程控制與字串
下面的敘述會傳回什麼?
SELECT 2000-01-01;
下面的敘述會傳回什麼?(字元集是 utf8mb4)
SELECT LENGTH('資料庫');
下面的敘述會傳回什麼?
SELECT CONCAT('Uedu', NULL);
數學函式與除法
| 函式 | 範例 | 結果 |
|---|---|---|
ROUND(n)、ROUND(n, d) |
ROUND(1.2345, 2) |
1.23:四捨五入到 d 位小數 |
FLOOR(n) |
FLOOR(-2.7) |
-3:不大於 n 的最大整數 |
CEILING(n)(或 CEIL) |
CEILING(2.1) |
3:不小於 n 的最小整數 |
MOD(n, m)(或 n % m) |
MOD(10, 3) |
1:餘數 |
n DIV m |
10 DIV 3 |
3:整數商 |
ABS、POWER、SQRT |
POWER(2, 10) |
1024 |
ROUND 對兩種數字的規則不同。 整數與 DECIMAL 是精確數值,.5 一律遠離 0 進位:ROUND(2.5) 是 3。
FLOAT、DOUBLE 是近似數值,交給系統的 C 函式庫處理,在多數系統上是「進位到偶數」:ROUND(2.5e0) 是 2。
assignment.hours 是 FLOAT,所以 8.5 小時用 ROUND 會得到 8。金額、成績這類要精確計算的資料,請用 DECIMAL 型別。
除法 / 的結果有小數。 兩個整數相除,結果是有 4 位小數的 DECIMAL:7 / 2 是 3.5000、10 / 3 是 3.3333。
這個 4 來自系統變數 div_precision_increment(預設 4),小數位數是被除數的位數再加 4,例如 1.0 / 3 是 0.33333。
除以 0 在 SELECT 裡得到 NULL 並出現警告 1365;在 INSERT、UPDATE 裡,MySQL 8 預設的嚴格模式會直接拒絕:ERROR 1365 (22012): Division by 0。
日期與時間函式
| 函式 | 範例(NOW() 是 2026-09-01 09:00:00) | 結果 |
|---|---|---|
NOW()、CURDATE()、CURTIME() |
CURDATE() |
2026-09-01 |
DATE_ADD(d, INTERVAL n 單位) |
DATE_ADD(CURDATE(), INTERVAL 30 DAY) |
2026-10-01 |
ADDDATE(d, INTERVAL …)、ADDDATE(d, 天數) |
ADDDATE('1999-01-01', INTERVAL '1-6' YEAR_MONTH) |
2000-07-01:加 1 年 6 個月 |
DATE_SUB(d, INTERVAL …) |
DATE_SUB('2026-09-01', INTERVAL 1 DAY) |
2026-08-31 |
DATEDIFF(d1, d2) |
DATEDIFF('2026-09-01', '2003-01-20') |
8625:d1 減 d2 的天數 |
YEAR(d)、MONTH(d)、DAY(d) |
MONTH('2003-01-20') |
1 |
DATE_FORMAT(d, 格式) |
DATE_FORMAT('2003-01-20', '%Y/%m/%d') |
2003/01/20 |
UNIX_TIMESTAMP(d) |
UNIX_TIMESTAMP('1970-01-02 00:00:00') |
86400(時區為 UTC 時) |
- INTERVAL 的單位有
DAY、MONTH、YEAR、HOUR、MINUTE等,也可以寫NOW() + INTERVAL 2 HOUR - 加月份遇到月底會自動調整:
'2026-01-31' + INTERVAL 1 MONTH是2026-02-28 DATE_FORMAT常用的格式:%Y四位數年、%m兩位數月、%d兩位數日、%H時(24 小時制)、%i分、%W星期的英文名稱UNIX_TIMESTAMP傳回從 1970-01-01 00:00:00 UTC 起算的秒數,會依照工作階段的時區解讀日期: 同一個'2000-07-01',時區是 UTC 時得到 962409600,在台灣時區(UTC+8)得到少 8 小時的 962380800
數學與日期函式
第一個敘述比較 DECIMAL 的 ROUND(2.5) 與 DOUBLE 的 ROUND(2.5e0);第二個敘述的除以 0 得到 NULL,結果下方有「1 warning」。第三個查詢對 FLOAT 欄位 hours 使用 ROUND。後面三個敘述是日期計算,NOW() 在播放器裡固定為 2026-09-01 09:00:00。
想一想:數字與日期
下面的敘述會傳回什麼?
SELECT 7 / 2;
assignment 只有一列,hours 是 FLOAT 型別,值為 8.5。下面的查詢會傳回什麼?
SELECT ROUND(hours) FROM assignment;
下面的敘述會傳回什麼?
SELECT DATE_ADD('2026-01-31', INTERVAL 1 MONTH);
動手寫:員工的中文職稱
請列出每位員工的 name、職稱的中文(欄位取名 role)與姓名的字元數(欄位取名 len),依 name 排序。職稱是 Programmer 時顯示「工程師」,DBA 顯示「資料庫管理員」,其他職稱顯示「其他」。
重點整理
| 主題 | 記住這些 |
|---|---|
| NULL | = NULL 永遠是 NULL,用 IS NULL、<=>;NOT IN 的清單裡有 NULL 就找不到資料 |
| 邏輯運算 | 三值邏輯;寫成 AND、OR、NOT,符號寫法(如 &&)已棄用 |
| 大小寫 | 預設不分;區分時用 COLLATE utf8mb4_bin 或 CAST(… AS BINARY),BINARY 'x' 自 8.0.27 起有警告 |
| 流程控制 | IF、CASE、IFNULL、COALESCE |
| 字串 | LENGTH 是位元組、CHAR_LENGTH 是字元;CONCAT 遇到 NULL 就是 NULL;LIKE 用 % 與 _ |
| 數學 | ROUND 對 DECIMAL 與 FLOAT 的規則不同;整數相除有 4 位小數;DIV 是整數商 |
| 日期 | 日期一定要加引號;DATE_ADD/ADDDATE 搭配 INTERVAL;DATEDIFF 算天數;UNIX_TIMESTAMP 受時區影響 |
下一課介紹 MySQL 的儲存引擎:InnoDB、MyISAM 等引擎的差別,以及全文檢索。