動態生成 SQL 時,傳統方式需同時修改 SELECT 欄位、FROM 表清單、WHERE 連接條件,業務邏輯複雜。本文展示**標量子查詢**僅修改 SELECT 清單即可關聯多表,降低動態 SQL 生成複雜度,並實測效能對比。
-- 學生主表
CREATE TABLE student (id NUMBER, name NVARCHAR2(10), sex CHAR(1), grade CHAR(1), age NUMBER);
-- 性別代碼表
CREATE TABLE sexcode (dm CHAR(1), mc NVARCHAR2(5));
-- 年級代碼表
CREATE TABLE gradecode (dm CHAR(1), mc NVARCHAR2(5));
INSERT INTO student VALUES (1,'張三','1','2',8);
INSERT INTO student VALUES (2,'李四','0','1',11);
INSERT INTO student VALUES (3,'王五','1','2',9);
INSERT INTO student VALUES (4,'劉二','0','4',8);
INSERT INTO student VALUES (5,'韓六','0','3',6);
INSERT INTO sexcode VALUES ('1','男'); INSERT INTO sexcode VALUES ('0','女');
INSERT INTO gradecode VALUES ('1','一年級'); INSERT INTO gradecode VALUES ('2','二年級'); INSERT INTO gradecode VALUES ('3','三年級');
SELECT s.id, s.name, sc.mc AS sex, gc.mc AS grade, s.age
FROM student s, sexcode sc, gradecode gc
WHERE sc.dm = s.sex(+) AND s.grade = gc.dm(+);
動態生成時需同時處理:SELECT 欄位清單、FROM 表清單、WHERE 連接條件、(+) 外連接標記,邏輯耦合度高。
SELECT s.id, s.name, s.age,
(SELECT mc FROM sexcode WHERE dm = s.sex) AS sex,
(SELECT mc FROM gradecode WHERE dm = s.grade) AS grade
FROM student s;
優勢:
(+)-- 兩種寫法耗時相當,標量子查詢略優
-- 原因:Oracle 優化器會將標量子查詢轉換為 LEFT JOIN(UNNESTING)
-- 可透過 EXPLAIN PLAN 驗證 Operation 出現 HASH JOIN OUTER 或 NESTED LOOPS OUTER
⚠️ **標量子查詢效能陷阱**:若子查詢表無索引、資料量大、或被調用次數極高(如主表百萬列),會退化為逐列 NESTED LOOPS,效能暴跌。大資料量場景**請改用標準 JOIN 或 MATERIALIZE HINT**。
| 場景 | 推薦寫法 | 理由 |
|---|---|---|
| 動態報表、可配置欄位、關聯表固定少量 | 標量子查詢 | 生成邏輯簡單、維護性高 |
| 主表 > 10 萬列、關聯表大、高頻查詢 | 標準 JOIN + 索引 | 穩定 Hash/Merge Join,避免逐列執行 |
| 需聚合統計(COUNT/SUM/AVG) | 標準 JOIN + GROUP BY | 標量子查詢無法直接聚合 |
| 關聯條件複雜、多欄位 JOIN | 標準 JOIN | 子查詢可讀性下降 |
WITH sex_map AS (SELECT dm, mc FROM sexcode),
grade_map AS (SELECT dm, mc FROM gradecode)
SELECT s.id, s.name, s.age,
(SELECT mc FROM sex_map WHERE dm = s.sex) AS sex,
(SELECT mc FROM grade_map WHERE dm = s.grade) AS grade
FROM student s;
標量子查詢是「以空間換時間」的寫法簡化技巧。**小表、固定關聯、動態生成** 是其最佳舞臺;大表高頻查詢請回歸標準 JOIN。
註:目前僅在 Oracle 驗證,其他資料庫請自行測試相容性。