首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> Oracle 資料庫 >> Oracle 資料庫基礎

Oracle 逗號分隔欄位轉列(行)單一 SQL 實作:CONNECT BY + 字串切割技巧

     編輯:Oracle 資料庫基礎

業務場景

單一欄位以逗號儲存多值(如權限代碼 'READ,WRITE,EXEC'),需查詢「擁有 WRITE 權限的使用者」。
LIKE '%,WRITE,%' 效能差且易誤判(如 WRITE_ADMIN),最佳解法是將逗號分隔字串展開為多列,再以等值比對或 JOIN。

核心 SQL(Oracle 專用)


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;

原理拆解

  1. ',' || c || ',':確保首尾皆有分隔符,統一切割邏輯。
  2. LENGTH(c||',') - LENGTH(REPLACE(c,',','')):計算逗號個數 = 切割段數。
  3. CONNECT BY LEVEL <= 100:產生 1~100 序號,作為切割迴圈計數器。
  4. 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)需修改應用寫入邏輯新系統設計、重構專案

XMLTABLE 寫法(11g+ 推薦)


SELECT user_id, TO_NUMBER(COLUMN_VALUE) AS perm_code
FROM user_perm,
     XMLTABLE(('"' || REPLACE(perm_codes, ',', '","') || '"'));

最佳實踐建議

  • 若系統長期需此類查詢,務必正規化建立關聯表,避免違反第一正規化(1NF)。
  • 既有系統無法重構時,建議建立 Materialized View 或 Function-Based Index 於切割邏輯上(12c+ 支援 JSON 索引)。
  • CONNECT BY LEVEL <= N 中的 N 設為業務上可能的最大值+安全邊際,避免資料遺漏。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved