單一欄位以逗號儲存多值(如權限代碼 'READ,WRITE,EXEC'),需查詢「擁有 WRITE 權限的使用者」。
LIKE '%,WRITE,%' 效能差且易誤判(如 WRITE_ADMIN),最佳解法是將逗號分隔字串展開為多列,再以等值比對或 JOIN。
SELECT a, b,
SUBSTR(t.ca, INSTR(t.ca, ',', 1, c.lv) + 1,
INSTR(t.ca, ',', 1, c.lv + 1) - INSTR(t.ca, ',', 1, c.lv) - 1) AS c
FROM (
SELECT a, b,
',' || c || ',' AS ca, -- 前後加逗號便於統一切割
LENGTH(c || ',') - NVL(LENGTH(REPLACE(c, ',', '')), 0) AS cnt
FROM test
) t,
(
SELECT LEVEL lv FROM dual CONNECT BY LEVEL <= 100 -- 最大切割段數,視實際調整
) c
WHERE c.lv <= t.cnt;
',' || c || ',':確保首尾皆有分隔符,統一切割邏輯。LENGTH(c||',') - LENGTH(REPLACE(c,',','')):計算逗號個數 = 切割段數。CONNECT BY LEVEL <= 100:產生 1~100 序號,作為切割迴圈計數器。INSTR(..., 1, lv):找第 lv 個逗號位置,配合 SUBSTR 取中間值。
-- 原表:user_perm(user_id, perm_codes) perm_codes 如 '101,102,105'
-- 需求:找出擁有權限 102 的使用者
WITH expanded AS (
SELECT user_id,
TO_NUMBER(
SUBSTR(t.ca, INSTR(t.ca, ',', 1, c.lv) + 1,
INSTR(t.ca, ',', 1, c.lv + 1) - INSTR(t.ca, ',', 1, c.lv) - 1)
) AS perm_code
FROM (
SELECT user_id, ',' || perm_codes || ',' AS ca,
LENGTH(perm_codes || ',') - NVL(LENGTH(REPLACE(perm_codes, ',', '')), 0) AS cnt
FROM user_perm
) t,
(SELECT LEVEL lv FROM dual CONNECT BY LEVEL <= 50) c
WHERE c.lv <= t.cnt
)
SELECT DISTINCT user_id FROM expanded WHERE perm_code = 102;
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| CONNECT BY 切割 | 純 SQL、無需建立物件、Oracle 9i+ 通用 | 大資料量 CPU 較高、最大段數需預估 | 中小表、臨時查詢、ETL 中轉 |
| XMLTABLE (11g+) | 語法簡潔、自動處理型別轉換 | 需 XML 解析開銷 | 11g+、結構化字串 |
| JSON_TABLE (12c+) | 標準 JSON 路徑、效能佳 | 需 12c+、資料需轉 JSON 格式 | 現代應用、REST 介面整合 |
| PL/SQL PIPELINED FUNCTION | 可複用、支援複雜邏輯 | 需建立物件、Context Switch 開銷 | 高頻複用、複雜切割規則 |
| 正規化設計 | 根治:建立關聯表 user_perm_rel(user_id, perm_code) | 需修改應用寫入邏輯 | 新系統設計、重構專案 |
SELECT user_id, TO_NUMBER(COLUMN_VALUE) AS perm_code
FROM user_perm,
XMLTABLE(('"' || REPLACE(perm_codes, ',', '","') || '"'));
CONNECT BY LEVEL <= N 中的 N 設為業務上可能的最大值+安全邊際,避免資料遺漏。