oracle connect by 遞歸查詢

基本語法

    start with :設置起點,省略後默認以全部行爲起點。
    connect by [condition] :與一般的條件一樣作用於當前列,但是在滿足條件後,會以全部列作爲下一層級遞歸(沒有其他條件的話)。
    prior : 表示上一層級的標識符。經常用來對下一層級的數據進行限制。不可以接僞列。
    level :僞列,表示當前深度。
    connect_by_root() :顯示根節點列。經常用來分組。
    connect_by_isleaf :1是葉子節點,0不是葉子節點。在製作樹狀表格時必用關鍵字。
    sys_connect_by_path() :將遞歸過程中的列進行拼接。
    nocycle , connect_by_iscycle : 在有循環結構的查詢中使用。
    siblings : 保留樹狀結構,對兄弟節點進行排序

舉幾個例子。
先建一張測試表。

    create table nayi_180328_connect_test(
    dept_id varchar2(50),
    parent_id varchar2(50),
    dept_name varchar2(100),
    dept_rank varchar2(2000),
    val number);
    
    insert into nayi_180328_connect_test
    select 'root', '', '全國', '', 0 from dual
    union all
    select 'root_1', 'root', '北京市', '', 2000 from dual
    union all
    select 'ln_root', 'root', '遼寧省', '', 200 from dual
    union all
    select 'ln_ys', 'ln_root', '遼寧省瀋陽市', '', 1000 from dual
    union all
    select 'ln_sy_hp', 'ln_ys', '遼寧省瀋陽和平區', '', 500 from dual
    union all
    select 'ln_ys_dd', 'ln_ys', '遼寧省瀋陽大東區', '', 600 from dual
    union all
    select 'jl_root', 'root', '吉林省', '', 0 from dual
    union all
    select 'jl_jl', 'jl_root', '吉林省吉林市', '', 200 from dual
    union all
    select 'jl_cc', 'jl_root', '吉林省長春市', '', 500 from dual
    ;
    commit;

  

數據:
DEPT_ID     PARENT_ID     DEPT_NAME     DEPT_RANK     VAL
root         全國         0
root_1     root     北京市         2000
ln_root     root     遼寧省         200
ln_ys     ln_root     遼寧省瀋陽市         1000
ln_sy_hp     ln_ys     遼寧省瀋陽和平區         500
ln_ys_dd     ln_ys     遼寧省瀋陽大東區         600
jl_root     root     吉林省         0
jl_jl     jl_root     吉林省吉林市         200
jl_cc     jl_root     吉林省長春市         500
無prior

    select t.*, level, CONNECT_BY_ROOT(num) root, lpad('---', level * 3 - 1, '-') || '>' || num num, connect_by_isleaf isleaf
      from (select 7 as num from dual
            union all
            select 8 as num from dual
            ) t
    connect by level <= 3;

  

NUM     LEVEL     ROOT     NUM_LEVEL     ISLEAF
7     1     7     –>7     0
7     2     7     ----->7     0
7     3     7     -------->7     1
8     3     7     -------->8     1
8     2     7     ----->8     0
7     3     7     -------->7     1
8     3     7     -------->8     1
8     1     8     –>8     0
7     2     8     ----->7     0
7     3     8     -------->7     1
8     3     8     -------->8     1
8     2     8     ----->8     0
7     3     8     -------->7     1
8     3     8     -------->8     1

可以很明顯的看出當滿足當前行條件(level ❤️)時,下一層爲全部行(兩行)。這是對於每一個“當前行”都生效的。
它也相當於對每一層級做層級數的笛卡爾積。即與下面的語句等效。

    with tab as (
    select 7 as num from dual
    union all
    select 8 as num from dual)
    
    select t1.*, 1 l_level from tab t1
    union all
    select t1.*, 2 from tab t1, tab t2
    union all
    select t1.*, 3 from tab t1, tab t2, tab t3;

   

當初始數據爲一行時最爲常用。此時會生成層級數目的行,再配合level生成數據。
例:

    select level * 2 "生成線性數字",
           to_char(add_months(sysdate, -level + 1), 'yyyymm') "當前月份前的n個月份",
           regexp_substr('/root/ln_root/ln_ys/ln_sy_hp', '[^/]+', 1, level) "配合正則表達式分隔字符串"
      from dual connect by level <= 5;

  

生成線性數字     當前月份前的n個月份     配合正則表達式分隔字符串
2     201804     root
4     201803     ln_root
6     201802     ln_ys
8     201801     ln_sy_hp
10     201712     
有prior

最簡單的樹狀查詢

    select t1.*
      from nayi_180328_connect_test t1
     where 1 = 1
     start with t1.dept_id = 'root'
    connect by prior t1.dept_id = t1.parent_id;

   

DEPT_ID     PARENT_ID     DEPT_NAME     DEPT_RANK     VAL
root         全國         0
jl_root     root     吉林省         0
jl_cc     jl_root     吉林省長春市         500
jl_jl     jl_root     吉林省吉林市         200
ln_root     root     遼寧省         200
ln_ys     ln_root     遼寧省瀋陽市         1000
ln_sy_hp     ln_ys     遼寧省瀋陽和平區         500
ln_ys_dd     ln_ys     遼寧省瀋陽大東區         600
root_1     root     北京市         2000

prior可以有多個
比如這樣

    select *
      from nayi_180328_connect_test t1
     where 1 = 1
     start with t1.dept_id = 'root'
    connect by prior t1.dept_id = t1.parent_id
           and prior t1.dept_id != prior t1.dept_name

   

表示當前行需滿足上一層級的id與name不可相等,顯然與第一句結果集相等。
或者這樣

    select *
      from nayi_180328_connect_test t1
     where 1 = 1
     start with t1.dept_id = 'root'
    connect by prior t1.dept_id = t1.parent_id
           and prior t1.dept_id || prior t1.parent_id != 'root'

   

表示上一行的id與name拼接後不能等於root,它只能查出起始行。

具體條件要根據實際需要去調整。

#### 查詢語句中的prior。
同樣查出上一層級的相應數據

    select t1.*,
           prior dept_id
      from nayi_180328_connect_test t1
     where 1 = 1
     start with t1.dept_id = 'root'
    connect by prior t1.dept_id = t1.parent_id
    ;

   

DEPT_ID     PARENT_ID     DEPT_NAME     DEPT_RANK     VAL     PRIORDEPT_ID
root         全國         0     
jl_root     root     吉林省         0     root
jl_cc     jl_root     吉林省長春市         500     jl_root
jl_jl     jl_root     吉林省吉林市         200     jl_root
ln_root     root     遼寧省         200     root
ln_ys     ln_root     遼寧省瀋陽市         1000     ln_root
ln_sy_hp     ln_ys     遼寧省瀋陽和平區         500     ln_ys
ln_ys_dd     ln_ys     遼寧省瀋陽大東區         600     ln_ys
root_1     root     北京市         2000     root

相當於是lead函數的簡易實現。缺陷是隻能查前一級。

    with nayi as (
    select t1.*,
           prior dept_id,
           connect_by_isleaf isleaf
      from nayi_180328_connect_test t1
     where 1 = 1
     start with t1.dept_id = 'root'
    connect by prior t1.dept_id = t1.parent_id
    )
    select distinct t1.*,
           lead(t1.dept_id, 1)
              over(partition by connect_by_root(t1.dept_id) order by level) p_dept,
           lead(t1.dept_id, 2)
              over(partition by connect_by_root(t1.dept_id) order by level) p_dept_2
      from nayi_180328_connect_test t1
     start with t1.dept_id in
                (select v1.dept_id from nayi v1 where v1.isleaf = 1)
    connect by prior t1.parent_id = t1.dept_id
    ;

  

DEPT_ID     PARENT_ID     DEPT_NAME     DEPT_RANK     VAL     P_DEPT     P_DEPT_2
ln_sy_hp     ln_ys     遼寧省瀋陽和平區         500     ln_ys_dd     ln_root
jl_root     root     吉林省         0     root     
root         全國         0         
ln_ys_dd     ln_ys     遼寧省瀋陽大東區         600     jl_jl     ln_root
jl_root     root     吉林省         0     ln_ys     
ln_root     root     遼寧省         200     root     
root         全國         0     root     
root         全國         0     ln_root     
jl_jl     jl_root     吉林省吉林市         200     jl_cc     root
ln_ys     ln_root     遼寧省瀋陽市         1000     root     root
jl_cc     jl_root     吉林省長春市         500     ln_ys     root
ln_ys     ln_root     遼寧省瀋陽市         1000     jl_root     root
root_1     root     北京市         2000     ln_sy_hp     
root         全國         0     jl_root     
ln_root     root     遼寧省         200     ln_root     
siblings

保留樹狀結果順序,並對兄弟節點進行排序

    select t1.*
      from nayi_180328_connect_test t1
     where 1 = 1
     start with t1.dept_id = 'root'
    connect by prior t1.dept_id = t1.parent_id
     order siblings by t1.val desc
    ;

  

DEPT_ID     PARENT_ID     DEPT_NAME     DEPT_RANK     VAL
root         全國         0
root_1     root     北京市         2000
ln_root     root     遼寧省         200
ln_ys     ln_root     遼寧省瀋陽市         1000
ln_ys_dd     ln_ys     遼寧省瀋陽大東區         600
ln_sy_hp     ln_ys     遼寧省瀋陽和平區         500
jl_root     root     吉林省         0
jl_cc     jl_root     吉林省長春市         500
jl_jl     jl_root     吉林省吉林市         200
sys_connect_by_path

常使用生成的數據來簡化其他查詢

    update (
    select /*+ BYPASS_UJVC */ t1.*, t2.dept_path
      from nayi_180328_connect_test t1,
           (select t1.dept_id, sys_connect_by_path(t1.dept_id, '/') dept_path
              from nayi_180328_connect_test t1
             where 1 = 1
             start with t1.dept_id = 'root'
            connect by prior t1.dept_id = t1.parent_id) t2
     where t1.dept_id = t2.dept_id
    ) t1 set t1.dept_rank = t1.dept_path
    ;
    commit;

  

DEPT_ID     PARENT_ID     DEPT_NAME     DEPT_RANK     VAL
root         全國     /root     0
root_1     root     北京市     /root/root_1     2000
ln_root     root     遼寧省     /root/ln_root     200
ln_ys     ln_root     遼寧省瀋陽市     /root/ln_root/ln_ys     1000
ln_sy_hp     ln_ys     遼寧省瀋陽和平區     /root/ln_root/ln_ys/ln_sy_hp     500
ln_ys_dd     ln_ys     遼寧省瀋陽大東區     /root/ln_root/ln_ys/ln_ys_dd     600
jl_root     root     吉林省     /root/jl_root     0
jl_jl     jl_root     吉林省吉林市     /root/jl_root/jl_jl     200
jl_cc     jl_root     吉林省長春市     /root/jl_root/jl_cc     500

例如通過dept_rank列來查詢遼寧省的全部節點

    select *
      from nayi_180328_connect_test t1
     start with t1.dept_id = 'ln_root'
    connect by prior t1.dept_id = t1.parent_id
    ;

  

DEPT_ID     PARENT_ID     DEPT_NAME     DEPT_RANK     VAL
ln_root     root     遼寧省     /root/ln_root     200
ln_ys     ln_root     遼寧省瀋陽市     /root/ln_root/ln_ys     1000
ln_sy_hp     ln_ys     遼寧省瀋陽和平區     /root/ln_root/ln_ys/ln_sy_hp     500
ln_ys_dd     ln_ys     遼寧省瀋陽大東區     /root/ln_root/ln_ys/ln_ys_dd     600

最後再舉一些我平時練習時使用的例子

枚舉法求最大公約數

    with a1 as (select level num from dual connect by level <= 100),
         a2 as (select level num from dual connect by level <= 64)
    select max(t1.n1) greatest_divisor
      from (select a1.num n1, a2.num n2
              from a1, a2
             where 1 = 1
               and mod(100, least(a1.num, a2.num)) = 0
               and mod(64, least(a1.num, a2.num)) = 0) t1
     where t1.n1 = t1.n2
    ;

  

結果爲4。

按組去除相同前綴

    with tab1 as (
    select '12388554gf' str, 1 group_id from dual
    union all
    select '12311' str, 1 from dual
    union all
    select '1234rg' str, 1 from dual
    union all
    select '1238822f' str, 1 from dual
    union all
    select 'asdf123' str, 2 from dual
    union all
    select 'asdf789' str, 2 from dual),
    tab2 as (
    select t1.str,
           t1.group_id,
           level - 1 ll,
           substr(t1.str, 1, level - 1) sub_str,
           count(1) over(partition by t1.group_id, level, substr(t1.str, 1, level - 1)) max_num
      from (select t1.str,
                   t1.group_id,
                   min(length(t1.str)) over(partition by t1.group_id) min_length,
                   count(1) over(partition by t1.group_id) row_num from tab1 t1) t1
    connect by level <= min_length + 1
           and prior t1.str = t1.str
           and prior sys_guid() is not null),
    tab3 as (
    select distinct first_value(t1.ll) over(partition by t1.group_id order by t1.max_num desc, t1.ll desc) sub_num,
           t1.group_id
    from tab2 t1)
    select t1.str,
           substr(t1.str,
                  (select max(sub_num)
                     from tab3 v1
                    where v1.group_id = t1.group_id) + 1) sub_str,
           t1.group_id
      from tab1 t1
    ;

  

STR     SUB_STR     GROUP_ID
12388554gf     88554gf     1
12311     11     1
1234rg     4rg     1
1238822f     8822f     1
asdf123     123     2
asdf789     789     2
總結

遞歸查詢主要使用的地方

    通過遞歸特點生成數據。
    構建樹狀表格。
    通過sys_connect_by_path生成的字段做其他處理。
    對數據進行樹狀梳理,使用各種關鍵字與函數實現想要的結果。
————————————————
版權聲明:本文爲CSDN博主「nayi_224」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/nayi_224/article/details/79811185

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章