返回文章列表

SQL子查詢與比較運算元詳解

深入探討 SQL 子查詢的進階應用與各種比較運算元的使用技巧。涵蓋子查詢多行結果處理、IN 與 NOT IN 運算元、範圍查詢 BETWEEN、模式匹配 LIKE,以及 NULL 值的正確處理方式,提供完整的資料篩選解決方案。

資料庫 SQL

在關聯式資料庫的操作中,精確的資料篩選是撰寫高品質 SQL 查詢的核心能力。子查詢作為一種強大的查詢技術,允許在一個查詢中嵌套另一個查詢,實現複雜的資料篩選邏輯。然而,當子查詢的結果不符合預期時,例如傳回多行資料而外層查詢只能處理單一值,就會導致執行錯誤。理解如何正確處理這些情況,以及熟練運用各種比較運算元,對於資料庫開發人員而言至關重要。

本文將系統性地探討 SQL 子查詢的進階應用,特別著重於多行結果的處理策略。同時,我們也將深入分析各種比較運算元的特性與使用場景,包括基本的等於與不等於運算元、範圍查詢的 BETWEEN、集合比較的 IN 與 NOT IN、模式匹配的 LIKE,以及 NULL 值處理的 IS NULL 與 IS NOT NULL。透過理論說明與實際案例的結合,讀者將能夠掌握這些工具,撰寫出更加靈活且高效的 SQL 查詢。

子查詢基礎與多行結果問題

子查詢是嵌套在另一個 SQL 陳述式中的查詢,它可以出現在 SELECT、FROM、WHERE 或 HAVING 子句中。子查詢的結果會被外層查詢使用,形成一種由內而外的執行邏輯。根據子查詢傳回的結果類型,可以分為標量子查詢(傳回單一值)、行子查詢(傳回單一行)和表子查詢(傳回多行多欄)。

當子查詢用於與單一值比較的情境時,例如使用等於運算元,該子查詢必須只傳回一個值。如果子查詢傳回多個值,資料庫系統將無法確定應該與哪一個值進行比較,因此會產生錯誤。

以下是一個典型的錯誤示範:

-- 建立示範用的國家與地區表
-- 國家表儲存國家基本資訊
CREATE TABLE country (
    country_id INT PRIMARY KEY,
    country_name VARCHAR(100) NOT NULL,
    population BIGINT,
    capital VARCHAR(100)
);

-- 地區表儲存各國的產酒地區資訊
-- country_id 參照國家表建立關聯
CREATE TABLE region (
    region_id INT PRIMARY KEY,
    region_name VARCHAR(100) NOT NULL,
    country_id INT,
    climate VARCHAR(50),
    FOREIGN KEY (country_id) REFERENCES country(country_id)
);

-- 插入測試資料
INSERT INTO country (country_id, country_name, population, capital) VALUES
(1, 'France', 67390000, 'Paris'),
(2, 'Spain', 46940000, 'Madrid'),
(3, 'USA', 331900000, 'Washington D.C.'),
(4, 'Italy', 60360000, 'Rome');

INSERT INTO region (region_id, region_name, country_id, climate) VALUES
(1, 'Napa Valley', 3, 'Mediterranean'),
(2, 'Walla Walla Valley', 3, 'Continental'),
(3, 'Texas Hill Country', 3, 'Semi-arid'),
(4, 'Bordeaux', 1, 'Oceanic'),
(5, 'Burgundy', 1, 'Continental'),
(6, 'Rioja', 2, 'Continental'),
(7, 'Tuscany', 4, 'Mediterranean');

-- 這個查詢會產生錯誤
-- 因為子查詢傳回多個 country_id(所有國家)
-- 但等於運算元期望只有一個值
SELECT region_name
FROM region
WHERE country_id = (
    SELECT country_id
    FROM country
    -- 沒有 WHERE 條件,會傳回所有國家的 ID
);

-- 錯誤訊息範例:
-- ERROR: more than one row returned by a subquery used as an expression
-- 或
-- ERROR 1242: Subquery returns more than 1 row

這個錯誤的根本原因在於運算元的語意不匹配。等於運算元 = 設計用於比較兩個單一值,但子查詢卻傳回了多個值。資料庫無法將一個值同時與多個值比較是否相等,因此報告錯誤。

使用 IN 運算元處理多行子查詢

解決子查詢傳回多行問題的標準方法是使用 IN 運算元。IN 運算元允許比較一個值是否存在於一組值中,因此可以自然地處理子查詢傳回多行的情況。

-- 使用 IN 運算元修正查詢
-- 查詢所有有對應國家資料的地區
SELECT region_name, climate
FROM region
WHERE country_id IN (
    SELECT country_id
    FROM country
);

-- 結果會傳回所有地區,因為每個地區的 country_id 都存在於 country 表中
-- region_name          | climate
-- ---------------------|--------------
-- Napa Valley          | Mediterranean
-- Walla Walla Valley   | Continental
-- Texas Hill Country   | Semi-arid
-- Bordeaux             | Oceanic
-- Burgundy             | Continental
-- Rioja                | Continental
-- Tuscany              | Mediterranean

IN 運算元的語意是「存在於集合中」,這與等於運算元的「等於特定值」完全不同。當使用 IN 時,資料庫會檢查外層查詢的每一行是否滿足「在子查詢結果中」的條件。

以下是更多實用的 IN 運算元應用範例:

-- 範例 1:查詢特定國家的地區
-- 使用子查詢動態決定要查詢哪些國家
SELECT region_name, climate
FROM region
WHERE country_id IN (
    SELECT country_id
    FROM country
    WHERE country_name IN ('France', 'Italy')
);

-- 結果:
-- region_name | climate
-- ------------|-------------
-- Bordeaux    | Oceanic
-- Burgundy    | Continental
-- Tuscany     | Mediterranean

-- 範例 2:查詢人口超過 5000 萬的國家的地區
SELECT region_name
FROM region
WHERE country_id IN (
    SELECT country_id
    FROM country
    WHERE population > 50000000
);

-- 範例 3:多層嵌套的子查詢
-- 查詢有生產 Merlot 葡萄酒的國家的所有地區
-- 假設有一個 wine 表記錄各地區的葡萄酒
SELECT region_name
FROM region
WHERE country_id IN (
    SELECT DISTINCT country_id
    FROM region
    WHERE region_id IN (
        SELECT region_id
        FROM wine
        WHERE wine_type = 'Merlot'
    )
);

使用 IN 運算元時需要注意幾個效能考量。首先,當子查詢傳回的結果集很大時,IN 運算元的效能可能會下降,因為資料庫需要對每一行進行集合成員檢查。在這種情況下,可以考慮使用 EXISTS 運算元或將子查詢改寫為 JOIN 操作。其次,如果子查詢結果中包含 NULL 值,IN 運算元的行為可能會出乎意料,這一點將在後續章節詳細說明。

NOT IN 運算元與 NULL 值陷阱

NOT IN 運算元用於選擇不存在於指定集合中的值,是 IN 運算元的邏輯否定。然而,NOT IN 在處理 NULL 值時有一個重要的陷阱,開發人員必須特別注意。

-- 基本的 NOT IN 使用
-- 查詢沒有記錄任何地區的國家
-- 這個查詢假設我們想找出哪些國家沒有產酒地區
SELECT country_name
FROM country
WHERE country_id NOT IN (
    SELECT country_id
    FROM region
);

-- 如果 region 表的 country_id 欄位沒有 NULL 值
-- 這個查詢會正常運作

-- 但如果我們插入一筆 country_id 為 NULL 的資料
INSERT INTO region (region_id, region_name, country_id, climate)
VALUES (8, 'Unknown Region', NULL, 'Unknown');

-- 現在相同的 NOT IN 查詢會傳回空結果!
SELECT country_name
FROM country
WHERE country_id NOT IN (
    SELECT country_id
    FROM region
);

-- 結果:空集合
-- 這是因為 NOT IN 遇到 NULL 值時的特殊行為

這個現象的原因與 SQL 的三值邏輯有關。當比較一個值與 NULL 時,結果既不是 TRUE 也不是 FALSE,而是 UNKNOWN。在 NOT IN 的語意中,如果集合中有任何一個 NULL 值,整個 NOT IN 表達式的結果就會變成 UNKNOWN,導致 WHERE 子句不滿足條件。

以下是解決 NULL 值問題的方法:

-- 方法 1:在子查詢中排除 NULL 值
SELECT country_name
FROM country
WHERE country_id NOT IN (
    SELECT country_id
    FROM region
    WHERE country_id IS NOT NULL
);

-- 方法 2:使用 NOT EXISTS(推薦)
-- NOT EXISTS 不受 NULL 值影響
SELECT country_name
FROM country c
WHERE NOT EXISTS (
    SELECT 1
    FROM region r
    WHERE r.country_id = c.country_id
);

-- 方法 3:使用 LEFT JOIN 配合 NULL 檢查
SELECT c.country_name
FROM country c
LEFT JOIN region r ON c.country_id = r.country_id
WHERE r.region_id IS NULL;

以下是 IN 與 NOT IN 運算元的處理流程示意圖:

@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

start

:執行子查詢\n取得結果集合;

if (結果集合\n包含 NULL?) then (是)
    if (使用 NOT IN?) then (是)
        :所有比較結果\n變成 UNKNOWN;
        :WHERE 條件\n不滿足;
        :傳回空結果;
        stop
    else (否)
        :NULL 值被\n視為不匹配;
        :繼續處理;
    endif
else (否)
endif

:逐一比較\n外層查詢的值;

if (值存在於集合?) then (是)
    if (使用 IN?) then (是)
        :符合條件;
    else (NOT IN)
        :不符合條件;
    endif
else (否)
    if (使用 IN?) then (是)
        :不符合條件;
    else (NOT IN)
        :符合條件;
    endif
endif

:傳回結果;

stop

@enduml

範圍查詢:BETWEEN 與 NOT BETWEEN

BETWEEN 運算元提供了一種簡潔的方式來查詢落在特定範圍內的值。它等同於使用兩個比較運算元的組合,但語法更加直觀易讀。BETWEEN 是包含邊界值的,也就是說 x BETWEEN a AND b 等同於 x >= a AND x <= b

-- 建立示範用的工作表
CREATE TABLE job (
    job_id INT PRIMARY KEY,
    job_title VARCHAR(100) NOT NULL,
    salary DECIMAL(10, 2),
    start_date DATE,
    department VARCHAR(50)
);

-- 插入測試資料
INSERT INTO job (job_id, job_title, salary, start_date, department) VALUES
(1, 'Software Engineer', 85000.00, '2024-01-15', 'Engineering'),
(2, 'Senior Developer', 120000.00, '2024-02-01', 'Engineering'),
(3, 'Data Analyst', 70000.00, '2024-01-20', 'Analytics'),
(4, 'Project Manager', 95000.00, '2024-03-10', 'Management'),
(5, 'DevOps Engineer', 100000.00, '2024-02-15', 'Engineering'),
(6, 'Junior Developer', 55000.00, '2024-04-01', 'Engineering');

-- 使用 BETWEEN 查詢薪資在特定範圍內的工作
SELECT job_title, salary, department
FROM job
WHERE salary BETWEEN 80000 AND 100000;

-- 結果:
-- job_title          | salary    | department
-- -------------------|-----------|-------------
-- Software Engineer  | 85000.00  | Engineering
-- Project Manager    | 95000.00  | Management
-- DevOps Engineer    | 100000.00 | Engineering

-- 等同於以下查詢
SELECT job_title, salary, department
FROM job
WHERE salary >= 80000 AND salary <= 100000;

BETWEEN 可以用於數值、日期和字串類型的比較。對於日期範圍查詢,BETWEEN 特別有用:

-- 查詢特定日期範圍內開始的工作
SELECT job_title, start_date
FROM job
WHERE start_date BETWEEN '2024-01-01' AND '2024-02-28';

-- 結果:
-- job_title          | start_date
-- -------------------|------------
-- Software Engineer  | 2024-01-15
-- Senior Developer   | 2024-02-01
-- Data Analyst       | 2024-01-20
-- DevOps Engineer    | 2024-02-15

-- 注意:日期比較時,BETWEEN 包含邊界值
-- 如果要排除結束日期,需要調整範圍或使用其他方法

NOT BETWEEN 用於查詢不在指定範圍內的值:

-- 查詢薪資不在 60000 到 90000 範圍內的工作
SELECT job_title, salary
FROM job
WHERE salary NOT BETWEEN 60000 AND 90000;

-- 結果:
-- job_title       | salary
-- ----------------|------------
-- Senior Developer| 120000.00
-- DevOps Engineer | 100000.00
-- Junior Developer| 55000.00

使用 BETWEEN 時需要注意的一點是,對於時間戳(timestamp)類型的欄位,邊界值的處理可能會產生問題。例如,start_date BETWEEN '2024-01-01' AND '2024-01-31' 可能不會包含 2024-01-31 當天有時間部分的記錄。在這種情況下,建議使用明確的比較運算元或調整結束邊界。

模式匹配:LIKE 運算元

LIKE 運算元允許使用萬用字元進行模式匹配,是處理文字搜尋的重要工具。LIKE 支援兩種萬用字元:百分號 % 代表任意數量的字元(包括零個),底線 _ 代表恰好一個字元。

-- 建立示範用的員工表
CREATE TABLE employee (
    employee_id INT PRIMARY KEY,
    employee_name VARCHAR(100) NOT NULL,
    email VARCHAR(100),
    phone VARCHAR(20),
    hire_date DATE,
    retirement_date DATE
);

-- 插入測試資料
INSERT INTO employee (employee_id, employee_name, email, phone, hire_date, retirement_date) VALUES
(1, 'John Smith', '[email protected]', '02-1234-5678', '2020-03-15', NULL),
(2, 'Jane Johnson', '[email protected]', '02-2345-6789', '2019-07-01', '2034-07-01'),
(3, 'Bob Williams', '[email protected]', '03-3456-7890', '2021-01-10', NULL),
(4, 'Alice Brown', '[email protected]', '02-4567-8901', '2018-11-20', '2029-11-20'),
(5, 'Charles Davis', '[email protected]', '04-5678-9012', '2022-05-01', NULL);

-- 使用 % 萬用字元搜尋名字包含特定字串的員工
-- 搜尋名字中包含 'son' 的員工
SELECT employee_name, email
FROM employee
WHERE employee_name LIKE '%son%';

-- 結果:
-- employee_name | email
-- --------------|----------------------
-- Jane Johnson  | [email protected]

-- 搜尋以特定字元開頭的名字
SELECT employee_name
FROM employee
WHERE employee_name LIKE 'J%';

-- 結果:
-- employee_name
-- --------------
-- John Smith
-- Jane Johnson

-- 搜尋以特定字元結尾的名字
SELECT employee_name
FROM employee
WHERE employee_name LIKE '%s';

-- 結果:
-- employee_name
-- --------------
-- Bob Williams
-- Charles Davis

底線萬用字元用於精確匹配特定位置的字元:

-- 使用 _ 萬用字元匹配特定長度的模式
-- 搜尋電話號碼格式為 02-XXXX-XXXX 的員工
SELECT employee_name, phone
FROM employee
WHERE phone LIKE '02-____-____';

-- 結果:
-- employee_name | phone
-- --------------|-------------
-- John Smith    | 02-1234-5678
-- Jane Johnson  | 02-2345-6789
-- Alice Brown   | 02-4567-8901

-- 搜尋電子郵件中使用者名稱恰好兩個字元的員工
SELECT employee_name, email
FROM employee
WHERE email LIKE '__._@%';

-- 這會匹配像 '[email protected]' 這樣的模式

結合多個萬用字元可以創建更複雜的搜尋模式:

-- 搜尋使用公司域名且使用者名稱長度至少 5 個字元的電子郵件
SELECT employee_name, email
FROM employee
WHERE email LIKE '_____%@company.com';

-- 搜尋名字第二個字元是 'o' 的員工
SELECT employee_name
FROM employee
WHERE employee_name LIKE '_o%';

-- 結果:
-- employee_name
-- --------------
-- John Smith
-- Bob Williams

NOT LIKE 用於排除符合特定模式的值:

-- 排除使用公司域名的電子郵件
SELECT employee_name, email
FROM employee
WHERE email NOT LIKE '%@company.com';

-- 結果:
-- employee_name | email
-- --------------|--------------------
-- Bob Williams  | [email protected]

使用 LIKE 時需要注意效能問題。當搜尋模式以萬用字元開頭(如 %keyword)時,資料庫無法使用索引,必須掃描整個表。如果效能是關鍵考量,應該盡量避免這種模式,或考慮使用全文檢索(Full-Text Search)功能。

NULL 值處理:IS NULL 與 IS NOT NULL

NULL 在 SQL 中是一個特殊值,表示資料不存在、不適用或未知。NULL 不等於任何值,包括它自己。這意味著常規的比較運算元無法用於 NULL 值的檢測,必須使用專門的 IS NULL 和 IS NOT NULL 運算元。

-- 錯誤的 NULL 檢查方式
-- 這些查詢不會傳回任何結果,即使表中有 NULL 值
SELECT employee_name
FROM employee
WHERE retirement_date = NULL;  -- 永遠不會成立

SELECT employee_name
FROM employee
WHERE retirement_date != NULL; -- 永遠不會成立

-- 正確的 NULL 檢查方式
-- 查詢尚未設定退休日期的員工
SELECT employee_name, hire_date
FROM employee
WHERE retirement_date IS NULL;

-- 結果:
-- employee_name  | hire_date
-- ---------------|------------
-- John Smith     | 2020-03-15
-- Bob Williams   | 2021-01-10
-- Charles Davis  | 2022-05-01

-- 查詢已設定退休日期的員工
SELECT employee_name, retirement_date
FROM employee
WHERE retirement_date IS NOT NULL;

-- 結果:
-- employee_name | retirement_date
-- --------------|----------------
-- Jane Johnson  | 2034-07-01
-- Alice Brown   | 2029-11-20

理解 NULL 在各種運算中的行為非常重要:

-- NULL 在算術運算中
-- 任何與 NULL 的算術運算結果都是 NULL
SELECT 5 + NULL;        -- 結果:NULL
SELECT NULL * 100;      -- 結果:NULL
SELECT NULL - NULL;     -- 結果:NULL

-- NULL 在比較運算中
-- 任何與 NULL 的比較結果都是 UNKNOWN
SELECT 5 = NULL;        -- 結果:UNKNOWN(在 MySQL 中顯示為 NULL)
SELECT NULL = NULL;     -- 結果:UNKNOWN
SELECT 5 <> NULL;       -- 結果:UNKNOWN

-- NULL 在邏輯運算中
SELECT TRUE AND NULL;   -- 結果:UNKNOWN
SELECT FALSE AND NULL;  -- 結果:FALSE
SELECT TRUE OR NULL;    -- 結果:TRUE
SELECT FALSE OR NULL;   -- 結果:UNKNOWN

在實際應用中,經常需要處理 NULL 值與預設值的轉換:

-- 使用 COALESCE 函式將 NULL 轉換為預設值
-- COALESCE 傳回第一個非 NULL 的引數
SELECT
    employee_name,
    COALESCE(retirement_date, '9999-12-31') AS effective_retirement_date
FROM employee;

-- 使用 IFNULL(MySQL)或 NVL(Oracle)
SELECT
    employee_name,
    IFNULL(retirement_date, '未設定') AS retirement_status
FROM employee;

-- 在排序時處理 NULL 值
-- 預設情況下,NULL 值在排序時可能出現在開頭或結尾
-- 可以使用 NULLS FIRST 或 NULLS LAST 明確指定位置
SELECT employee_name, retirement_date
FROM employee
ORDER BY retirement_date NULLS LAST;

組合運算元實現複雜查詢

在實際應用中,經常需要組合多個比較運算元來實現複雜的資料篩選邏輯。理解運算元的優先順序和邏輯組合是撰寫正確查詢的關鍵。

-- 複合條件查詢範例
-- 查詢符合以下所有條件的員工:
-- 1. 薪資在 60000 到 100000 之間
-- 2. 在工程部門工作
-- 3. 姓名以 'S' 或 'D' 開頭
-- 4. 已設定退休日期

-- 首先建立包含薪資資訊的擴充員工資料
-- 這裡使用 CTE(Common Table Expression)模擬擴充資料
WITH employee_extended AS (
    SELECT
        e.employee_id,
        e.employee_name,
        j.salary,
        j.department,
        e.retirement_date
    FROM employee e
    JOIN job j ON e.employee_id = j.job_id
)
SELECT
    employee_name,
    salary,
    department,
    retirement_date
FROM employee_extended
WHERE salary BETWEEN 60000 AND 100000
  AND department = 'Engineering'
  AND (employee_name LIKE 'S%' OR employee_name LIKE 'D%')
  AND retirement_date IS NOT NULL;

-- 使用 IN 簡化多個 OR 條件
SELECT job_title, salary, department
FROM job
WHERE department IN ('Engineering', 'Analytics')
  AND salary >= 70000
  AND job_title NOT LIKE '%Junior%';

-- 結果:
-- job_title          | salary    | department
-- -------------------|-----------|-------------
-- Software Engineer  | 85000.00  | Engineering
-- Senior Developer   | 120000.00 | Engineering
-- Data Analyst       | 70000.00  | Analytics
-- DevOps Engineer    | 100000.00 | Engineering

使用子查詢實現更複雜的業務邏輯:

-- 查詢薪資高於部門平均的工作
SELECT
    job_title,
    salary,
    department
FROM job j
WHERE salary > (
    SELECT AVG(salary)
    FROM job
    WHERE department = j.department
);

-- 查詢薪資排名前 3 的工作(使用子查詢計數)
SELECT job_title, salary
FROM job j1
WHERE (
    SELECT COUNT(*)
    FROM job j2
    WHERE j2.salary > j1.salary
) < 3
ORDER BY salary DESC;

-- 使用 EXISTS 檢查關聯資料是否存在
-- 查詢有員工的部門
SELECT DISTINCT department
FROM job j
WHERE EXISTS (
    SELECT 1
    FROM employee e
    WHERE e.employee_id = j.job_id
);

以下是複合條件查詢的邏輯流程示意圖:

@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

start

:取得資料列;

:評估第一個條件\n(例如:BETWEEN);

if (條件 1 成立?) then (TRUE)
    :評估第二個條件\n(例如:IN);

    if (條件 2 成立?) then (TRUE)
        :評估第三個條件\n(例如:LIKE);

        if (條件 3 成立?) then (TRUE)
            :評估第四個條件\n(例如:IS NOT NULL);

            if (條件 4 成立?) then (TRUE)
                :包含在結果集中;
            else (FALSE)
                :排除;
            endif
        else (FALSE)
            :排除;
        endif
    else (FALSE)
        :排除;
    endif
else (FALSE)
    :排除;
endif

:處理下一列;

stop

@enduml

查詢最佳化與效能考量

在使用子查詢和比較運算元時,效能是一個重要的考量因素。以下是一些最佳化建議:

-- 最佳化建議 1:避免在子查詢中使用 SELECT *
-- 不推薦
SELECT employee_name
FROM employee
WHERE employee_id IN (
    SELECT *  -- 傳回所有欄位
    FROM job
    WHERE salary > 100000
);

-- 推薦
SELECT employee_name
FROM employee
WHERE employee_id IN (
    SELECT job_id  -- 只傳回需要的欄位
    FROM job
    WHERE salary > 100000
);

-- 最佳化建議 2:考慮將子查詢改寫為 JOIN
-- 子查詢版本
SELECT e.employee_name
FROM employee e
WHERE e.employee_id IN (
    SELECT j.job_id
    FROM job j
    WHERE j.salary > 100000
);

-- JOIN 版本(通常效能更好)
SELECT DISTINCT e.employee_name
FROM employee e
INNER JOIN job j ON e.employee_id = j.job_id
WHERE j.salary > 100000;

-- 最佳化建議 3:使用 EXISTS 替代 IN 處理大型結果集
-- IN 版本(需要載入整個子查詢結果)
SELECT country_name
FROM country
WHERE country_id IN (
    SELECT country_id
    FROM region
);

-- EXISTS 版本(找到第一個匹配就停止)
SELECT country_name
FROM country c
WHERE EXISTS (
    SELECT 1
    FROM region r
    WHERE r.country_id = c.country_id
);

-- 最佳化建議 4:為常用的搜尋欄位建立索引
-- 在 LIKE 搜尋的欄位上建立索引
-- 但注意:以萬用字元開頭的模式無法使用索引
CREATE INDEX idx_employee_name ON employee(employee_name);
CREATE INDEX idx_job_salary ON job(salary);
CREATE INDEX idx_job_department ON job(department);

常見錯誤與除錯技巧

在使用子查詢和比較運算元時,有一些常見的錯誤模式需要避免:

-- 錯誤 1:忽略 NULL 值的影響
-- 問題:NOT IN 遇到 NULL 會傳回空結果
-- 解決:使用 NOT EXISTS 或排除 NULL

-- 錯誤 2:BETWEEN 邊界值的錯誤理解
-- 問題:以為 BETWEEN 不包含邊界值
-- 實際:BETWEEN 包含兩端的邊界值

-- 錯誤 3:LIKE 模式的轉義問題
-- 如果要搜尋包含 % 或 _ 的字串,需要轉義
SELECT * FROM table_name WHERE column LIKE '%10\%%' ESCAPE '\';
-- 這會搜尋包含 '10%' 的字串

-- 錯誤 4:使用 = 而不是 IN 處理多行子查詢
-- 確認子查詢可能傳回的行數,選擇適當的運算元

-- 錯誤 5:比較不同資料類型
-- 確保比較的值具有相容的資料類型
-- 字串與數字比較可能導致隱式型別轉換,影響效能

總結而言,子查詢與比較運算元是 SQL 資料篩選的核心工具。透過正確理解 IN、NOT IN、BETWEEN、LIKE 和 IS NULL 等運算元的語意和特性,開發人員可以撰寫出既正確又高效的查詢。特別需要注意 NULL 值的特殊行為,以及在處理大型資料集時的效能考量。持續練習這些技巧,並根據實際情況選擇最適合的方法,是成為優秀資料庫開發人員的關鍵。