在資料庫查詢的實務工作中,精確篩選資料的能力直接決定了應用程式的效能與正確性。MySQL 提供的 AND 和 OR 邏輯運算子是構建複雜查詢條件的基礎工具,然而許多開發者在使用這些運算子時,經常因為忽略運算子優先順序而產生非預期的查詢結果。這種錯誤在生產環境中可能導致嚴重的資料處理問題,因此深入理解這些運算子的行為特性至關重要。

除了邏輯運算子之外,MySQL 還提供了豐富的內建函式庫,涵蓋數學運算、字串處理、日期時間操作等各種面向。這些函式能夠大幅簡化資料處理的複雜度,並且透過函式巢狀的技術,開發者可以組合多個函式來實作更進階的資料操作邏輯。本文將從基礎概念開始,逐步深入探討這些核心功能的實務應用方式。

邏輯運算子基礎概念

在 SQL 查詢語言中,WHERE 子句用於指定資料篩選的條件,而 AND 和 OR 則是連結多個條件的邏輯運算子。理解這兩個運算子的語意是撰寫正確查詢的第一步。AND 運算子要求所有連結的條件都必須為真,查詢結果才會包含該筆資料;OR 運算子則只要任一條件為真,該筆資料就會被納入結果集中。

從集合論的角度來看,AND 運算子相當於集合的交集運算,它會回傳同時滿足所有條件的資料子集。OR 運算子則相當於集合的聯集運算,會回傳滿足任一條件的所有資料。這種集合運算的觀點有助於開發者在設計複雜查詢時,更清楚地理解查詢結果的組成方式。

在實際應用中,單純使用 AND 或 OR 運算子通常不會造成問題,但當兩者混合使用時,運算子優先順序就成為關鍵議題。MySQL 遵循標準 SQL 規範,將 AND 運算子的優先順序設定為高於 OR 運算子,這意味著在沒有括號明確指定的情況下,AND 條件會先被評估。

運算子優先順序深入解析

運算子優先順序是程式語言中的普遍概念,在 MySQL 中也同樣適用。當一個查詢條件包含多個邏輯運算子時,MySQL 會依照預設的優先順序來決定條件的評估順序。這個機制類似於數學運算中乘除優先於加減的規則,AND 運算子的行為類似於乘法,而 OR 運算子的行為則類似於加法。

讓我們透過一個求職者資料表的範例來說明這個概念。假設有一個名為 applicant 的資料表,包含求職者的基本資訊以及學歷和工作經驗等欄位。資料表的結構如下所示,其中包含姓名、副學士學位旗標、學士學位旗標以及工作經驗年數等欄位。

-- 建立求職者資料表,用於示範邏輯運算子的使用
-- 此資料表包含求職者的基本資訊與資格條件
CREATE TABLE applicant (
    -- 求職者唯一識別碼,自動遞增
    id INT AUTO_INCREMENT PRIMARY KEY,
    -- 求職者姓名,最長100個字元
    name VARCHAR(100) NOT NULL,
    -- 是否擁有副學士學位,布林值
    associates_degree_flag BOOLEAN DEFAULT FALSE,
    -- 是否擁有學士學位,布林值
    bachelors_degree_flag BOOLEAN DEFAULT FALSE,
    -- 工作經驗年數,整數型別
    years_experience INT DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 插入測試資料,包含各種不同條件的求職者
-- 這些資料將用於示範 AND 和 OR 運算子的行為差異
INSERT INTO applicant (name, associates_degree_flag, bachelors_degree_flag, years_experience)
VALUES
    ('張小明', TRUE, FALSE, 3),   -- 擁有副學士學位,3年經驗
    ('李小華', FALSE, TRUE, 7),   -- 擁有學士學位,7年經驗
    ('王大衛', TRUE, TRUE, 5),    -- 擁有雙學位,5年經驗
    ('陳小芳', FALSE, TRUE, 1),   -- 擁有學士學位,1年經驗
    ('林小志', TRUE, FALSE, 2),   -- 擁有副學士學位,2年經驗
    ('黃小美', FALSE, FALSE, 10); -- 無學位,10年經驗

當我們需要找出具有至少兩年工作經驗,並且擁有副學士學位或學士學位的求職者時,一個常見的錯誤寫法如下所示。

-- 錯誤的查詢寫法:未考慮運算子優先順序
-- 此查詢的實際行為與預期不符
SELECT *
FROM applicant
WHERE years_experience >= 2
AND associates_degree_flag IS TRUE
OR bachelors_degree_flag IS TRUE;

這個查詢看起來似乎合理,但執行結果卻會包含只有一年經驗但擁有學士學位的陳小芳。問題在於 MySQL 會先評估 AND 條件,形成一個條件群組,然後再與 OR 條件進行聯集運算。因此,這個查詢實際上被解讀為:回傳所有(具有至少兩年經驗且擁有副學士學位的求職者)或者(擁有學士學位的求職者)。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

title MySQL 運算子優先順序解析

rectangle "原始查詢條件" as origin {
    rectangle "years_experience >= 2" as cond1
    rectangle "associates_degree_flag IS TRUE" as cond2
    rectangle "bachelors_degree_flag IS TRUE" as cond3
}

rectangle "MySQL 實際解讀" as parsed {
    rectangle "AND 群組\n(優先評估)" as and_group {
        rectangle "經驗 >= 2 年" as p1
        rectangle "副學士學位" as p2
    }
    rectangle "OR 連結" as or_link
    rectangle "學士學位" as p3
}

rectangle "預期解讀" as expected {
    rectangle "經驗 >= 2 年" as e1
    rectangle "AND" as and_link
    rectangle "OR 群組" as or_group {
        rectangle "副學士學位" as e2
        rectangle "學士學位" as e3
    }
}

origin -down-> parsed : 錯誤寫法
origin -down-> expected : 正確寫法

@enduml

要修正這個問題,必須使用括號明確指定 OR 條件的群組範圍,讓 MySQL 知道我們希望先評估括號內的條件,然後再與外部的 AND 條件結合。

-- 正確的查詢寫法:使用括號控制運算順序
-- 明確指定 OR 條件應該先被群組
SELECT *
FROM applicant
WHERE years_experience >= 2
AND (associates_degree_flag IS TRUE OR bachelors_degree_flag IS TRUE);

這個修正後的查詢會正確地回傳所有具有至少兩年工作經驗,並且擁有副學士學位或學士學位的求職者。括號的使用不僅解決了優先順序的問題,也讓查詢的意圖更加清晰,有助於後續的程式碼維護。

複雜條件組合的實務應用

在實際的業務場景中,查詢條件往往比上述範例更加複雜。例如,一個登機系統需要驗證乘客的身分證明文件,要求乘客必須持有駕照,並且同時擁有學生證或社會安全卡。這種需求涉及多重條件的邏輯組合,如果不仔細處理運算子優先順序,很容易產生錯誤。

-- 建立登機驗證資料表
-- 用於記錄乘客的身分證明文件持有狀態
CREATE TABLE boarding (
    -- 乘客記錄唯一識別碼
    id INT AUTO_INCREMENT PRIMARY KEY,
    -- 乘客姓名
    passenger_name VARCHAR(100) NOT NULL,
    -- 是否持有駕照
    license_flag BOOLEAN DEFAULT FALSE,
    -- 是否持有學生證
    student_id_flag BOOLEAN DEFAULT FALSE,
    -- 是否持有社會安全卡
    soc_sec_card_flag BOOLEAN DEFAULT FALSE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 插入測試資料
INSERT INTO boarding (passenger_name, license_flag, student_id_flag, soc_sec_card_flag)
VALUES
    ('旅客甲', TRUE, TRUE, FALSE),   -- 持有駕照和學生證
    ('旅客乙', TRUE, FALSE, TRUE),   -- 持有駕照和社會安全卡
    ('旅客丙', FALSE, TRUE, TRUE),   -- 持有學生證和社會安全卡,無駕照
    ('旅客丁', TRUE, FALSE, FALSE),  -- 只有駕照
    ('旅客戊', FALSE, FALSE, TRUE);  -- 只有社會安全卡

針對上述需求,錯誤的查詢寫法與正確的查詢寫法分別如下。

-- 錯誤的查詢寫法
-- 由於運算子優先順序,此查詢會包含所有持有社會安全卡的乘客
SELECT *
FROM boarding
WHERE license_flag IS TRUE
AND student_id_flag IS TRUE
OR soc_sec_card_flag IS TRUE;

-- 正確的查詢寫法
-- 使用括號確保只選出持有駕照且(持有學生證或社會安全卡)的乘客
SELECT *
FROM boarding
WHERE license_flag IS TRUE
AND (student_id_flag IS TRUE OR soc_sec_card_flag IS TRUE);

在更複雜的場景中,可能需要多層巢狀的括號來表達查詢邏輯。例如,一個人力資源系統需要找出符合特定條件的應徵者:必須具有程式設計經驗,並且(擁有碩士學位或者(擁有學士學位且工作經驗超過五年))。

-- 複雜的多層條件查詢
-- 展示如何使用巢狀括號表達複雜的業務邏輯
SELECT *
FROM candidates
WHERE programming_experience IS TRUE
AND (
    masters_degree_flag IS TRUE
    OR (
        bachelors_degree_flag IS TRUE
        AND years_experience > 5
    )
);

這種多層括號的寫法雖然功能正確,但可讀性較差。在實務中,建議將複雜的條件拆分成多個簡單的子查詢或使用 Common Table Expression 來提升程式碼的可維護性。

內建函式概觀與分類

MySQL 提供了數百個內建函式,這些函式涵蓋了資料處理的各個面向,包括數學運算、字串操作、日期時間處理、流程控制、聚合統計等。善用這些函式可以大幅簡化查詢邏輯,減少應用程式端的資料處理負擔,同時提升整體的執行效能。

函式的基本概念是接收輸入值(稱為引數或參數),經過特定的處理邏輯後,回傳一個結果值。有些函式不需要任何引數,例如 pi 函式會直接回傳圓周率的值;有些函式需要一個或多個必要引數;還有些函式支援選擇性引數,可以根據需求提供額外的參數來獲得更精確的結果。

MySQL 的內建函式可以大致分為以下幾個主要類別:數學函式用於執行各種數學運算,如四捨五入、取絕對值、計算平方根等;字串函式用於處理文字資料,如轉換大小寫、擷取子字串、計算字串長度等;日期時間函式用於處理時間相關的資料,如計算日期差異、格式化日期、擷取時間元素等;流程控制函式用於實作條件邏輯,如 IF、CASE、COALESCE 等;聚合函式用於對一組資料進行統計運算,如計算總和、平均值、最大最小值等。

數學函式實務應用

數學函式是 MySQL 中最基礎也最常用的函式類別之一。這些函式提供了各種數學運算的能力,從簡單的四捨五入到複雜的三角函數計算都有支援。在財務報表、科學計算、統計分析等應用場景中,數學函式扮演著不可或缺的角色。

pi 函式是最簡單的數學函式之一,它不需要任何引數,直接回傳圓周率的值。這個函式在計算圓面積、圓周長或其他與圓相關的幾何運算時非常有用。

-- 使用 pi 函式取得圓周率
-- 此函式不需要任何引數
SELECT pi();
-- 執行結果:3.141593

-- 計算半徑為 5 的圓面積
-- 應用公式:面積 = π × 半徑²
SELECT pi() * 5 * 5 AS circle_area;
-- 執行結果:78.539816...

round 函式用於將數值四捨五入到指定的小數位數。這個函式接受一個必要引數(要處理的數值)和一個選擇性引數(小數位數)。如果不提供第二個引數,函式會將數值四捨五入到最接近的整數。

-- 基本的四捨五入:不指定小數位數
-- 預設會四捨五入到整數
SELECT round(2.71828);
-- 執行結果:3

-- 指定四捨五入到兩位小數
-- 第二個引數指定保留的小數位數
SELECT round(2.71828, 2);
-- 執行結果:2.72

-- 指定四捨五入到四位小數
SELECT round(2.71828, 4);
-- 執行結果:2.7183

-- 使用負數作為第二個引數
-- 會四捨五入到十位、百位等
SELECT round(23.298, -1);
-- 執行結果:20

SELECT round(1234.5678, -2);
-- 執行結果:1200

除了 round 函式之外,MySQL 還提供了 floor 和 ceiling 函式,分別用於無條件捨去和無條件進位。這些函式在處理分頁邏輯、庫存計算等場景時經常被使用。

-- floor 函式:無條件捨去到最接近的較小整數
SELECT floor(2.9);
-- 執行結果:2

SELECT floor(-2.1);
-- 執行結果:-3

-- ceiling 函式:無條件進位到最接近的較大整數
SELECT ceiling(2.1);
-- 執行結果:3

SELECT ceiling(-2.9);
-- 執行結果:-2

-- 實務應用:計算分頁數
-- 假設總記錄數為 97,每頁顯示 10 筆
SELECT ceiling(97 / 10) AS total_pages;
-- 執行結果:10

字串函式實務應用

字串函式在處理文字資料時扮演著核心角色。這些函式可以用於轉換大小寫、擷取子字串、搜尋替換、計算長度等各種字串操作。在資料清理、格式標準化、搜尋功能實作等場景中,字串函式都是不可或缺的工具。

upper 和 lower 函式用於轉換字串的大小寫,這在進行不分大小寫的比對或標準化資料格式時非常有用。

-- upper 函式:將字串轉換為大寫
SELECT upper('hello world');
-- 執行結果:HELLO WORLD

-- lower 函式:將字串轉換為小寫
SELECT lower('HELLO WORLD');
-- 執行結果:hello world

-- 實務應用:不分大小寫的搜尋
-- 將搜尋欄位和搜尋條件都轉換為相同大小寫進行比對
SELECT *
FROM products
WHERE lower(product_name) = lower('iPhone');

concat 函式用於將多個字串串接在一起,這在組合姓名、建立完整地址或產生動態訊息時經常被使用。

-- concat 函式:串接多個字串
SELECT concat('Hello', ' ', 'World');
-- 執行結果:Hello World

-- 實務應用:組合姓名欄位
SELECT concat(last_name, ' ', first_name) AS full_name
FROM employees;

-- concat_ws 函式:使用指定分隔符串接字串
-- ws 代表 with separator
SELECT concat_ws(', ', city, district, street) AS full_address
FROM addresses;

substring 函式用於從字串中擷取指定位置和長度的子字串,這在解析固定格式的資料時非常有用。

-- substring 函式:擷取子字串
-- 語法:SUBSTRING(string, start_position, length)
-- 位置從 1 開始計算

-- 從第 1 個字元開始,擷取 5 個字元
SELECT substring('Hello World', 1, 5);
-- 執行結果:Hello

-- 從第 7 個字元開始,擷取到字串結尾
SELECT substring('Hello World', 7);
-- 執行結果:World

-- 實務應用:從身分證字號擷取性別碼
-- 假設身分證格式為 A123456789,第二個字元為性別碼
SELECT substring(id_number, 2, 1) AS gender_code
FROM citizens;

length 和 char_length 函式用於計算字串的長度。length 回傳的是位元組數,而 char_length 回傳的是字元數,在處理多位元組字元集(如 UTF-8)時,這兩個函式的結果可能不同。

-- length 函式:計算字串的位元組數
SELECT length('Hello');
-- 執行結果:5

SELECT length('你好');
-- 執行結果:6(UTF-8 編碼下,每個中文字元佔用 3 個位元組)

-- char_length 函式:計算字串的字元數
SELECT char_length('Hello');
-- 執行結果:5

SELECT char_length('你好');
-- 執行結果:2

-- 實務應用:驗證欄位長度
SELECT *
FROM products
WHERE char_length(product_code) != 10;

日期時間函式實務應用

日期時間函式是資料庫應用中最重要的函式類別之一,因為幾乎所有的業務系統都需要處理時間相關的資料。MySQL 提供了豐富的日期時間函式,可以用於取得當前時間、計算日期差異、格式化日期、擷取時間元素等各種操作。

now 函式用於取得當前的日期和時間,這在記錄資料建立時間、更新時間或進行時間比較時非常常用。

-- now 函式:取得當前的日期和時間
SELECT now();
-- 執行結果:2025-11-27 14:30:45(取決於執行時的實際時間)

-- curdate 函式:只取得當前日期
SELECT curdate();
-- 執行結果:2025-11-27

-- curtime 函式:只取得當前時間
SELECT curtime();
-- 執行結果:14:30:45

-- 實務應用:記錄資料建立時間
INSERT INTO orders (customer_id, order_date, total_amount)
VALUES (123, now(), 1500.00);

datediff 函式用於計算兩個日期之間相差的天數,這在計算年齡、會員資格有效期、專案進度等場景中非常有用。

-- datediff 函式:計算日期差異
-- 語法:DATEDIFF(date1, date2)
-- 回傳 date1 - date2 的天數

-- 計算兩個日期之間的天數
SELECT datediff('2025-12-25', '2025-11-27');
-- 執行結果:28

-- 實務應用:計算會員註冊天數
SELECT
    member_name,
    registration_date,
    datediff(curdate(), registration_date) AS days_since_registration
FROM members;

-- 實務應用:找出過去 30 天內的訂單
SELECT *
FROM orders
WHERE datediff(curdate(), order_date) <= 30;

date_format 函式用於將日期時間值格式化為指定的字串格式,這在產生報表或顯示給使用者時非常有用。

-- date_format 函式:格式化日期時間
-- 使用格式代碼指定輸出格式

-- 格式化為 年-月-日
SELECT date_format(now(), '%Y-%m-%d');
-- 執行結果:2025-11-27

-- 格式化為 年/月/日 時:分:秒
SELECT date_format(now(), '%Y/%m/%d %H:%i:%s');
-- 執行結果:2025/11/27 14:30:45

-- 格式化為中文格式
SELECT date_format(now(), '%Y年%m月%d日');
-- 執行結果:2025年11月27日

-- 常用的格式代碼:
-- %Y:四位數年份(如 2025)
-- %y:兩位數年份(如 25)
-- %m:兩位數月份(01-12)
-- %d:兩位數日期(01-31)
-- %H:兩位數小時(00-23)
-- %i:兩位數分鐘(00-59)
-- %s:兩位數秒數(00-59)
-- %W:星期名稱(如 Monday)
-- %M:月份名稱(如 November)

year、month、day 等函式用於從日期時間值中擷取特定的元素,這在進行分組統計或篩選特定時間範圍時非常有用。

-- 擷取日期時間元素

-- year 函式:擷取年份
SELECT year('2025-11-27');
-- 執行結果:2025

-- month 函式:擷取月份
SELECT month('2025-11-27');
-- 執行結果:11

-- day 函式:擷取日期
SELECT day('2025-11-27');
-- 執行結果:27

-- hour、minute、second 函式:擷取時間元素
SELECT hour('14:30:45'), minute('14:30:45'), second('14:30:45');
-- 執行結果:14, 30, 45

-- 實務應用:依月份統計銷售額
SELECT
    year(order_date) AS order_year,
    month(order_date) AS order_month,
    sum(total_amount) AS monthly_sales
FROM orders
GROUP BY year(order_date), month(order_date)
ORDER BY order_year, order_month;

函式巢狀技術

函式巢狀是一種強大的技術,它允許將一個函式的回傳值作為另一個函式的輸入引數。透過函式巢狀,開發者可以組合多個函式的功能,實作更複雜的資料處理邏輯,而不需要撰寫多個獨立的查詢或在應用程式端進行額外的處理。

函式巢狀的基本概念是從內層函式開始執行,將其結果傳遞給外層函式。這個執行順序與數學運算中先計算括號內的表達式相同。理解這個執行順序對於正確使用函式巢狀至關重要。

-- 基本的函式巢狀:將 pi() 的結果傳入 round()
-- 執行順序:先執行 pi() 得到 3.141593,再執行 round() 得到 3
SELECT round(pi());
-- 執行結果:3

-- 指定小數位數的函式巢狀
SELECT round(pi(), 2);
-- 執行結果:3.14

SELECT round(pi(), 4);
-- 執行結果:3.1416
@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

title 函式巢狀執行順序

rectangle "SELECT round(pi(), 2)" as query

rectangle "執行步驟" as steps {
    rectangle "步驟 1" as step1 {
        rectangle "執行 pi()" as exec_pi
        rectangle "回傳 3.141593" as result_pi
    }

    rectangle "步驟 2" as step2 {
        rectangle "執行 round(3.141593, 2)" as exec_round
        rectangle "回傳 3.14" as result_round
    }
}

rectangle "最終結果:3.14" as final

query -down-> step1
step1 -down-> step2
step2 -down-> final

@enduml

在實務應用中,函式巢狀經常被用於資料清理和格式標準化。例如,將使用者輸入的姓名轉換為標準格式(首字母大寫,其餘小寫),或者清除字串前後的空白並轉換大小寫。

-- 多層函式巢狀:清理並格式化字串
-- 先使用 trim() 移除前後空白,再使用 lower() 轉換為小寫

-- trim 函式:移除字串前後的空白字元
SELECT trim('  Hello World  ');
-- 執行結果:Hello World

-- 組合 trim 和 lower
SELECT lower(trim('  HELLO WORLD  '));
-- 執行結果:hello world

-- 實務應用:標準化電子郵件地址
-- 移除空白並轉換為小寫,確保格式一致
UPDATE users
SET email = lower(trim(email))
WHERE email IS NOT NULL;

函式巢狀也可以用於日期時間的複雜計算。例如,計算某個日期所在月份的最後一天,或者取得下個月的第一天。

-- 日期時間函式的巢狀應用

-- 取得當前月份的最後一天
-- 使用 last_day() 函式
SELECT last_day(curdate());
-- 執行結果:2025-11-30(假設當前日期為 2025-11-27)

-- 取得下個月的第一天
-- 先取得本月最後一天,再加一天
SELECT date_add(last_day(curdate()), INTERVAL 1 DAY);
-- 執行結果:2025-12-01

-- 取得當前季度的第一天
-- 組合 year()、quarter() 和 str_to_date() 函式
SELECT str_to_date(
    concat(year(curdate()), '-', (quarter(curdate()) - 1) * 3 + 1, '-01'),
    '%Y-%m-%d'
);
-- 執行結果:2025-10-01(假設當前日期為 2025-11-27,第四季)

在財務計算的場景中,函式巢狀可以用於計算複雜的數值結果。例如,計算折扣後的金額並四捨五入到整數。

-- 財務計算的函式巢狀應用

-- 計算九折後的金額並四捨五入
SELECT round(1234.56 * 0.9);
-- 執行結果:1111

-- 計算含稅金額(稅率 5%)並四捨五入到小數點後兩位
SELECT round(1000 * 1.05, 2);
-- 執行結果:1050.00

-- 實務應用:計算訂單的折扣金額
SELECT
    order_id,
    original_amount,
    discount_rate,
    round(original_amount * (1 - discount_rate), 2) AS final_amount
FROM orders;

條件函式與流程控制

除了基本的數學、字串和日期時間函式之外,MySQL 還提供了條件函式和流程控制功能,讓開發者可以在查詢中實作條件邏輯。這些函式包括 IF、CASE、COALESCE、NULLIF 等,它們可以根據不同的條件回傳不同的結果。

IF 函式是最基本的條件函式,它接受三個引數:條件表達式、條件為真時的回傳值、條件為假時的回傳值。

-- IF 函式:基本的條件判斷
-- 語法:IF(condition, value_if_true, value_if_false)

-- 判斷數值正負
SELECT if(10 > 0, '正數', '非正數');
-- 執行結果:正數

-- 實務應用:根據庫存量顯示狀態
SELECT
    product_name,
    stock_quantity,
    if(stock_quantity > 0, '有庫存', '無庫存') AS stock_status
FROM products;

-- 實務應用:根據成績判斷是否及格
SELECT
    student_name,
    score,
    if(score >= 60, '及格', '不及格') AS result
FROM exam_results;

CASE 表達式提供了更複雜的條件邏輯,可以處理多個條件分支。CASE 有兩種語法形式:簡單 CASE 表達式和搜尋 CASE 表達式。

-- 簡單 CASE 表達式:比對特定值
SELECT
    product_name,
    category_id,
    CASE category_id
        WHEN 1 THEN '電子產品'
        WHEN 2 THEN '服飾配件'
        WHEN 3 THEN '食品飲料'
        ELSE '其他'
    END AS category_name
FROM products;

-- 搜尋 CASE 表達式:評估條件表達式
SELECT
    student_name,
    score,
    CASE
        WHEN score >= 90 THEN 'A'
        WHEN score >= 80 THEN 'B'
        WHEN score >= 70 THEN 'C'
        WHEN score >= 60 THEN 'D'
        ELSE 'F'
    END AS grade
FROM exam_results;

-- 實務應用:根據訂單金額計算運費
SELECT
    order_id,
    total_amount,
    CASE
        WHEN total_amount >= 1000 THEN 0
        WHEN total_amount >= 500 THEN 50
        ELSE 100
    END AS shipping_fee
FROM orders;

COALESCE 函式用於回傳引數列表中第一個非 NULL 的值,這在處理可能包含 NULL 值的欄位時非常有用。

-- COALESCE 函式:回傳第一個非 NULL 值
SELECT coalesce(NULL, NULL, 'Hello', 'World');
-- 執行結果:Hello

-- 實務應用:提供預設值
SELECT
    product_name,
    coalesce(discount_price, original_price) AS display_price
FROM products;

-- 實務應用:組合多個聯絡方式
SELECT
    customer_name,
    coalesce(mobile_phone, home_phone, office_phone, '無電話') AS contact_phone
FROM customers;

查詢效能最佳化考量

在使用邏輯運算子和內建函式時,必須考慮對查詢效能的影響。某些寫法可能會導致索引失效,進而造成全表掃描,嚴重影響查詢效能。理解這些效能陷阱並採取適當的因應措施,是撰寫高效能 SQL 查詢的關鍵。

當在 WHERE 子句中對欄位使用函式時,MySQL 無法使用該欄位上的索引。這是因為索引是根據欄位的原始值建立的,而不是函式處理後的值。

-- 效能不佳的寫法:對欄位使用函式會導致索引失效
SELECT *
FROM orders
WHERE year(order_date) = 2025;

-- 效能較佳的寫法:使用範圍查詢保持索引有效
SELECT *
FROM orders
WHERE order_date >= '2025-01-01' AND order_date < '2026-01-01';

-- 效能不佳的寫法:對欄位使用 lower() 函式
SELECT *
FROM users
WHERE lower(email) = 'test@example.com';

-- 效能較佳的寫法:如果可能,在應用程式端處理大小寫
-- 或者建立函式索引(MySQL 8.0+)

OR 運算子的使用也需要注意效能影響。當 OR 連結的條件涉及不同的欄位時,MySQL 可能無法有效地使用索引,導致查詢效能下降。在某些情況下,將 OR 查詢改寫為 UNION 可以獲得更好的效能。

-- 使用 OR 的查詢可能無法有效使用索引
SELECT *
FROM products
WHERE category_id = 1 OR brand_id = 5;

-- 改寫為 UNION 可能獲得更好的效能
-- 每個子查詢可以分別使用各自的索引
SELECT * FROM products WHERE category_id = 1
UNION
SELECT * FROM products WHERE brand_id = 5;

在處理大量資料時,函式巢狀的複雜度也會影響查詢效能。每一層函式呼叫都需要額外的運算時間,因此在設計查詢時應該盡量減少不必要的函式巢狀層數。

-- 複雜的函式巢狀會增加運算負擔
SELECT round(sqrt(abs(power(value, 2) - power(baseline, 2))), 2)
FROM measurements;

-- 如果這個計算需要頻繁執行,考慮:
-- 1. 在應用程式端進行計算
-- 2. 使用計算欄位或觸發器預先計算並儲存結果
-- 3. 使用 MySQL 8.0+ 的 Generated Columns 功能

實務案例整合應用

讓我們透過一個完整的實務案例來整合本文介紹的各種技術。假設我們正在開發一個電子商務系統,需要產生一份銷售報表,包含訂單資訊、客戶資料以及各種統計指標。

-- 建立訂單資料表
CREATE TABLE orders (
    -- 訂單唯一識別碼
    order_id INT AUTO_INCREMENT PRIMARY KEY,
    -- 客戶識別碼
    customer_id INT NOT NULL,
    -- 訂單日期時間
    order_date DATETIME NOT NULL,
    -- 訂單原始金額
    original_amount DECIMAL(10, 2) NOT NULL,
    -- 折扣率
    discount_rate DECIMAL(4, 3) DEFAULT 0,
    -- 訂單狀態:pending, confirmed, shipped, delivered, cancelled
    order_status VARCHAR(20) NOT NULL,
    -- 付款方式
    payment_method VARCHAR(20),
    -- 配送地址
    shipping_address VARCHAR(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 建立客戶資料表
CREATE TABLE customers (
    -- 客戶唯一識別碼
    customer_id INT AUTO_INCREMENT PRIMARY KEY,
    -- 客戶姓名
    customer_name VARCHAR(100) NOT NULL,
    -- 電子郵件
    email VARCHAR(100),
    -- 手機號碼
    mobile_phone VARCHAR(20),
    -- 會員等級:regular, silver, gold, platinum
    membership_level VARCHAR(20) DEFAULT 'regular',
    -- 註冊日期
    registration_date DATE NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 複雜的銷售報表查詢
-- 整合邏輯運算子、內建函式和函式巢狀
SELECT
    -- 訂單基本資訊
    o.order_id,
    date_format(o.order_date, '%Y/%m/%d') AS formatted_date,

    -- 客戶資訊
    c.customer_name,
    coalesce(c.email, '未提供') AS email,

    -- 金額計算
    o.original_amount,
    round(o.original_amount * o.discount_rate, 2) AS discount_amount,
    round(o.original_amount * (1 - o.discount_rate), 2) AS final_amount,

    -- 會員天數
    datediff(curdate(), c.registration_date) AS membership_days,

    -- 會員等級顯示
    CASE c.membership_level
        WHEN 'platinum' THEN '白金會員'
        WHEN 'gold' THEN '金卡會員'
        WHEN 'silver' THEN '銀卡會員'
        ELSE '一般會員'
    END AS membership_display,

    -- 訂單狀態顯示
    CASE o.order_status
        WHEN 'delivered' THEN '已送達'
        WHEN 'shipped' THEN '配送中'
        WHEN 'confirmed' THEN '已確認'
        WHEN 'pending' THEN '待處理'
        WHEN 'cancelled' THEN '已取消'
        ELSE '未知狀態'
    END AS status_display,

    -- 是否為高價值訂單
    if(o.original_amount >= 5000, '是', '否') AS high_value_order

FROM orders o
JOIN customers c ON o.customer_id = c.customer_id

-- 篩選條件:使用括號控制邏輯運算順序
WHERE (
    -- 條件群組一:高價值訂單或白金會員訂單
    o.original_amount >= 5000
    OR c.membership_level = 'platinum'
)
AND (
    -- 條件群組二:已完成的訂單
    o.order_status = 'delivered'
    OR o.order_status = 'shipped'
)
AND (
    -- 條件群組三:本月訂單
    year(o.order_date) = year(curdate())
    AND month(o.order_date) = month(curdate())
)

-- 排序
ORDER BY o.order_date DESC, o.original_amount DESC;

這個查詢範例展示了如何將邏輯運算子的正確使用、各種內建函式的應用、函式巢狀技術以及條件函式整合在一個實際的業務場景中。透過適當的括號使用,我們可以清楚地表達複雜的篩選邏輯;透過函式的組合應用,我們可以在單一查詢中完成所有必要的資料處理和格式化工作。

結語

MySQL 的 AND 和 OR 邏輯運算子是構建查詢條件的基礎工具,而正確理解運算子優先順序並使用括號控制條件群組,是避免查詢錯誤的關鍵。在實務開發中,養成使用括號明確表達查詢意圖的習慣,不僅可以避免潛在的邏輯錯誤,也可以提升程式碼的可讀性和可維護性。

MySQL 提供的豐富內建函式庫大幅簡化了資料處理的複雜度,從基本的數學運算到複雜的日期時間處理,這些函式都能在資料庫層級直接完成資料的轉換和計算。透過函式巢狀技術,開發者可以組合多個函式的功能,實作更進階的資料操作邏輯。然而,在追求功能實現的同時,也必須注意查詢效能的考量,避免因不當使用函式而導致索引失效或效能下降。

掌握這些核心技術後,開發者將能夠撰寫出更精確、更有效率的 SQL 查詢,為應用程式的資料處理需求提供穩固的基礎。隨著對 MySQL 功能的深入理解,開發者還可以探索更進階的主題,如預存程序、觸發器、視圖等,進一步提升資料庫應用的開發能力。