(一)遞歸
一個過程或函數在其定義或說明中有直接或間接調用自身的一種方法。
菲波那切數列就是利用遞歸定義的:
F0 = 0
F1 = 1
Fn = F(n – 1 )+ F(n – 2)
(二)遞歸查詢
使用遞歸查詢,需要确定初始參數和傳回值。
Oracle 資料庫長期以來一直通過專用文法(CONNECT BY 子句)支援遞歸。Oracle Database 11g 第 2 版通過子查詢分解來支援遞歸,這就為解決下面的老問題提供了一個更好的新方法:查詢層次結構資料。
1、基本文法
select … from tablename
start with 條件1
connect by 條件2
where 條件3;
一般用來查找存在父子關系的資料,也就是樹形結構的資料;其返還的資料也能夠明确的區分出每一層的資料。
- start with condition1 是用來限制第一層的資料,或者叫根節點資料;以這部分資料為初始來查找第二層資料,然後以第二層資料查找第三層資料以此類推。注意:start with不但可以指定一個根節點,還可以指定多個根節點。若該子句被省略,則表示所有滿足查詢條件的行作為根節點。
- connect by [prior] id=parentid :表示連接配接條件,這部分是用來指明oracle在查找資料時以怎樣的一種關系去查找;比如說查找第二層的資料時用第一層資料的id去跟表裡面記錄的parentid字段進行比對,如果這個條件成立那麼查找出來的資料就是第二層資料,同理查找第三層第四層…等等都是按這樣去比對。
- 關鍵詞prior用于指定查詢的方向,prior旁邊放子節點表示往下周遊;prior旁邊放父節點表示往上周遊,即關鍵詞prior旁邊放什麼字段,就表示往該字段方向周遊。
- 條件3 是過濾條件,用于對傳回的所有記錄進行過濾。
- connect_by_isleaf(僞列)表示遞歸中周遊的節點是否包含子節點,如果包含,傳回0;不包含,傳回1。
- level(僞列)表示遞歸查詢中的層級,值越小層級越高,level=1為層級最高節點,從1開始,往後依次遞推。
- sys_connect_by_path(column,‘分隔符’)函數表示以指定符号clear描述父節點到子節點的路徑。其中column是字元型或能自動轉換成字元型的列名,它的主要目的就是将父節點到目前節點的“path”按照指定的模式出現,char可以是單字元也可以是多字元,但不能使用列值中包含的字元,而且這個參數必須是常量,且不允許使用綁定變量,clear不要用逗号。注意:分隔符要用單引号
- sys_connect_by_path函數就是從start with開始的地方開始周遊,并記下其周遊到的節點,start with開始的地方被視為根節點,将周遊到的路徑根據函數中的分隔符,組成一個新的字元串。
select title,substr(sys_connect_by_path(id,'->'),3) "id層級" from tb_menu m connect by prior m.id = m.parent start with m.id =12;
注意:substr函數用于去除開頭的->符号。
(三)子查詢分解
子查詢的使用可以進入另一個層面。考慮對視圖的查詢。從概念上而言,一個視圖定義一個可對其執行查詢的結果表。假設可以編寫一個表達式,進而允許一個名稱與結果表相關聯。則使用該名稱的查詢将是一個對該結果表的查詢。子查詢分解(也稱為公用表表達式)正是這一思想的展現。
WITH 子句為子查詢塊指派一個名稱。之後可以使用指派的名稱在某個查詢中引用該查詢塊。
1、子查詢文法
with alias_name1 as (subquery1),
[ alias_name2 as (subQuery2), …… , alias_nameN as (subQueryN)]
select col1,col2…… col3 from alias_name1,alias_name2……,alias_nameN ;
注意:alias_name1表示别名。
命名子查詢包含兩個通過 UNION ALL 操作組合的查詢塊。第一個查詢塊是一個初始化子查詢(也稱定位點),其編碼是非遞歸的,包括确定調查起始點的種源。系統将首先處理這個子查詢。第二個查詢塊是遞歸子查詢,它根據與結果中已有行的關系向結果添加行。此處的技巧是定義新行與舊行的關聯方式。新行是通過将命名查詢與定位點确定的原始表進行聯接而識别的。UNION ALL 将定位點與遞歸子查詢進行組合,確定不從結果中清除重複記錄。這兩個查詢塊必須是可相容合并的;也就是說,兩個查詢塊中必須選擇相同的列數。
子查詢分解示例
with tmp as
(select a.*, substr(sys_connect_by_path(a.id, '/'), 2) cenji, level leaf
from tb_menu a
start with a.parent is null
connect by a.parent = prior a.id)
select * from tmp where leaf = (select leaf from tmp where id = 50);
查詢結果:
(四)遞歸周遊過程:
1、 周遊流程簡單介紹如下:
在掃描樹結構表時,需要依此通路樹結構的每個節點,一個節點隻能通路一次,其通路的步驟如下:
第一步:從根節點開始;
第二步:通路該節點;
第三步:判斷該節點有無未被通路的子節點,若有,則轉向它最左側的未被通路的子節,并執行第二步,否則執行第四步;
第四步:若該節點為根節點,則通路完畢,否則執行第五步;
第五步:傳回到該節點的父節點,并執行第三步驟。
總之:掃描整個樹結構的過程也即是中序周遊樹的過程。
2、樹結構的描述
樹結構的資料存放在表中,資料之間的層次關系即父子關系,通過表中的列與列間的關系來描述,如EMP表中的EMPNO和MGR。EMPNO表示該雇員的編号,MGR表示上司該雇員的人的編号,即子節點的MGR值等于父節點的EMPNO值。在表的每一行中都有一個表示父節點的MGR(除根節點外),通過每個節點的父節點,就可以确定整個樹結構。
例如:從根節點自頂向下:
select empno, mgr, level as lv from scott.emp a
start with mgr is null
connect by (prior empno) = mgr
order by level;
3、節點和分支的裁剪
在對樹結構進行查詢時,可以去掉表中的某些行,也可以剪掉樹中的一個分支,使用WHERE子句來限定樹型結構中的單個節點,以去掉樹中的單個節點,但它卻不影響其後代節點(自頂向下檢索時)或前輩節點(自底向頂檢索時)。
4、排序顯示
和其它查詢一樣,在樹結構查詢中也可以使用ORDER BY 子句,改變查詢結果的顯示順序,而不必按照周遊樹結構的順序。
(五)存儲過程
1、複雜的查詢展示需要使用存儲過程來實作,示例如下。
存儲過程學習
執行個體:
create table article
(
id number primary key,
cont varchar2(4000),
pid number,
isleaf number(1), --0代表非葉子節點,1代表葉子節點
alevel number(2)
)
-------------
insert into article values (1, '螞蟻大戰大象', 0, 0, 0);
insert into article values (2, '大象被打趴下了', 1, 0, 1);
insert into article values (3, '螞蟻也不好過', 2, 1, 2);
insert into article values (4, '瞎說', 2, 0, 2);
insert into article values (5, '沒有瞎說', 4, 1, 3);
insert into article values (6, '怎麼可能', 1, 0, 1);
insert into article values (7, '怎麼沒可能', 6, 1, 2);
insert into article values (8, '可能性是很大的', 6, 1, 2);
insert into article values (9, '大象進醫院了', 2, 0, 2);
insert into article values (10, '護士是螞蟻', 9, 1, 3);
commit;
---------
螞蟻大戰大象
大象被打趴下了
螞蟻也不好過
瞎說
沒有瞎說
大象進醫院了
護士是螞蟻
怎麼可能
怎麼不可能
可能性是很大的
--------------------------
create or replace procedure p (v_pid article.pid%type, v_level binary_integer) is
cursor c is select * from article where pid = v_pid;
v_preStr varchar2(1024) := '';
begin
for i in 1..v_level loop
v_preStr := v_preStr || '****';
end loop;
for v_article in c loop
dbms_output.put_line(v_preStr || v_article.cont);
if (v_article.isleaf = 0)
then
p (v_article.id, v_level + 1);
end if;
end loop;
end;