by Vadim Tropashko
?
翻譯:
Janwer Zhang
關(guān)系數(shù)據(jù)庫通常被認(rèn)為是在其先輩網(wǎng)絡(luò)和分層模型上的進(jìn)步發(fā)展。在每個層級查詢方面,當(dāng)模型轉(zhuǎn)換成依賴關(guān)系時,他們結(jié)果是驚人地不完整。幾乎每兩三個月總有關(guān)于如何在數(shù)據(jù)庫中建立樹模型的問題彈出在comp.database.theory新聞組。在本文中我將探討兩者用四個眾所周知的方法的實(shí)現(xiàn),并展示它們之間的關(guān)聯(lián)。我們將找到一個可以被看作是具體路徑(materialized path)和嵌套集合(nested sets)“混合式”的新方法。
鏈接表(Adjacency List)
樹結(jié)構(gòu)是有向無環(huán)圖(Directed Acyclic Graph, 簡稱DAG)的一個特殊案例。描繪DAG結(jié)構(gòu)的方式之一:
create table emp ( ename varchar2(100), mgrname varchar2(100 );emp 表的每條記錄通過指向上級mgrname的ename來標(biāo)識。例如,假如JONES向KING報告,于是emp表中含有<ename='JONES', mgrname='KING'>的記錄。假設(shè),emp表也包含<ename='SCOTT', mgrname='JONES'>。此外,假如emp表不含有<ename='SCOTT',mgrname='KING'>記錄,對于其它每對毗連的記錄也是如此,那么它就是所謂的鄰接表(Adjacency List)。如果正好相反,那emp表是可傳遞的閉包關(guān)系。
一個典型的層次查詢可能會詢問SCOTT是否間接向KING報告。由于我們不知道兩者間的層級數(shù)字,因此我們不能告訴emp表要進(jìn)行多少次自連接,以至于這個任務(wù)不能用傳統(tǒng)的SQL解決。假如知道emp表是傳遞閉包tcemp,那么這個查詢是小事一樁:
select 'TRUE' from tcemp where ename='SCOTT' and mgrname='KING'這個簡便查詢的犧牲代價 transitive closure maintenance .
此外,SQL擴(kuò)展:SQL3/DB2遞歸查詢,能執(zhí)行層次查詢:
with tcemp as (select ename,mgrname from tcemp union select tcemp.ename,emp.mgrname from tcemp, emp where tcemp.mgrname = emp.ename) select 'TRUE' from tcempwhere ename = 'SCOTT' and mgrname = 'KING';這個tcemp計(jì)算作為中間關(guān)聯(lián),或采用Oracle專有連接的語法:
select 'TRUE' from ( select ename from emp connect by prior mgrname = ename start with ename = 'SCOTT') where ename = 'KING';
其中內(nèi)查詢"chases the pointers"從SCOTT節(jié)點(diǎn)到樹的根節(jié)點(diǎn),而外查詢檢查KING節(jié)點(diǎn)是否在路徑上。
鏈接表可以說是最直觀的樹模型。這是我們的主要焦點(diǎn),不過,接下來還有兩種方法。
具體化路徑(Materialized Path)
在這種做法中每條記錄存儲到根部為止的整個路徑。在我們前面的例子中,讓我們假定KING為根節(jié)點(diǎn),然后,記錄 ename="SCOTT" 通過路徑 SCOTT->JONES->KING 連接到根部。現(xiàn)代數(shù)據(jù)庫允許描繪一個節(jié)點(diǎn)清單作為一個單一的值,但由于具體路徑在被發(fā)明之前的長時間里,約定停留在經(jīng)由一些分隔符連接的普通字符串節(jié)點(diǎn),最常見的'.'或'/。在后一種情況下,尤其明顯一個類似UNIX文件系統(tǒng)的路徑名。
在這種做法中每條記錄存儲到根部為止的整個路徑。在我們前面的例子中,讓我們假定KING為根節(jié)點(diǎn),然后,記錄 ename="SCOTT" 通過路徑 SCOTT->JONES->KING 連接到根部。現(xiàn)代數(shù)據(jù)庫允許描繪一個節(jié)點(diǎn)清單作為一個單一的值,但由于具體路徑在被發(fā)明之前的長時間里,約定停留在經(jīng)由一些分隔符連接的普通字符串節(jié)點(diǎn),最常見的'.'或'/。在后一種情況下,尤其明顯一個類似UNIX文件系統(tǒng)的路徑名。
應(yīng)用更緊湊的變量方法,是在字符路徑里我們使用兄弟分子代替節(jié)點(diǎn)的主鍵。擴(kuò)展我們的例子:
ENAME | PATH |
KING | 1 |
JONES | 1.1 |
SCOTT | 1.1.1 |
ADAMS | 1.1.1.1 |
FORD | 1.1.2 |
SMITH | 1.1.2.1 |
BLAKE | 1.2 |
ALLEN | 1.2.1 |
WARD | 1.2.2 |
CLARK | 1.3 |
MILLER | 1.3.1 |
Path 1.1.2 指示FORD是父節(jié)點(diǎn)JONES的第二個孩子節(jié)點(diǎn)
讓我們寫一些查詢。
1. 雇員FORD和它的一系列上級節(jié)點(diǎn):
select e1.ename from emp e1, emp e2 where e2.path like e1.path || '%' and e2.ename='FORD'?
2. 雇員JONES及它的所有間接子節(jié)點(diǎn):
select e1.ename from emp e1, emp e2 where e1.path like e2.path || '%' and e2.ename='JONES'?
盡管兩個查詢看起來是對稱的,但在它們的各自的執(zhí)行中有根本性的差別。如果一顆子樹的下級節(jié)點(diǎn)相比整體層次大小而言是較小的,那么在數(shù)據(jù)庫中通過執(zhí)行主鍵抓取e2記錄,然后執(zhí)行e1.path范圍的掃描,這是快速的保證。
在另一方面,上級節(jié)點(diǎn)的查詢大體上是相同的
select e1.ename from emp e1, emp e2 where e2.path > e1.path and e2.path < e1.path || 'Z' and e2.ename='FORD'?
或者,本來知道e2.path,請注意,它可以進(jìn)一小減少到:
select e1.ename from emp e1 where e2.path>e1.path and e2.path<e1.path || 'Z'?
這里,很顯然path上的索引不會起作用(除了e2.path恰好是靠近域邊界的意外情況下,以便有選擇性的判定e2.path>e1.path)。明顯的解決方法是,我們并沒有利用數(shù)據(jù)庫去計(jì)算出所有的上級路徑!例如,1.1.2的上級是1.1和1。一個簡單的遞歸字符串解析函數(shù)可以提取這些路徑,那么回答上層的名稱通過:
select e1.ename from emp where e1.path in ('1.1','1')這應(yīng)該是個快速級聯(lián)的執(zhí)行方案。
嵌套集合(Nested Sets)
具體路徑和
Joe Celko的嵌套集合
均具有標(biāo)準(zhǔn)SQL語法層次查詢的回應(yīng)能力。在兩種模型中,節(jié)點(diǎn)的全局位置在層次中是“編碼”的,相反鏈接表的每個連接僅是一個近鄰間的局部連接。類似于具體路徑,嵌套集合模型也遭遇上層節(jié)點(diǎn)查詢的性能問題。
select p2.emp from Personnel p1, Personnel p2where p1.lft between p2.lft and p2.rgtand p1.emp = 'Chuck'(注意:這個查詢借自 previously cited Celko article )
此處,這問題變得比具體路徑情況下更明確:我們需要找出特定點(diǎn)的所有間隔。這個問題很難解決。盡管有像R-Tree的專門索引表,但它們中沒有一個能像B- Tree一樣被普遍接受。例如,如果頂層路徑僅包含10個節(jié)點(diǎn),而整棵樹的大小為1000000,那么沒有一種索引技術(shù)能夠提供 1000000/10=100000倍的性能提升。(這樣的性能改進(jìn)因素通常與索引掃描范圍類似,非常有選擇性,以數(shù)據(jù)卷為條件的典型關(guān)聯(lián)。)
不像一個具體路徑,這邊的技巧是我們 計(jì)算所有節(jié)點(diǎn)而不須為查詢數(shù)據(jù)庫做不必要的工作。
另一個--較根本性的--嵌套集合的缺點(diǎn)是嵌套集編碼是暫時性的。如果我們在分層結(jié)構(gòu)中間插入一個 節(jié)點(diǎn),插入點(diǎn)邊界以上的所有間隔必須重新計(jì)算。換句話說,當(dāng)我們插入一條記錄到數(shù)據(jù)庫中,大概有一半左右的其它記錄需要被更新。這也是為什么嵌套集合模型僅能接收有限的靜態(tài)層次。
嵌套集合間的區(qū)間為整數(shù)。為嘗試使嵌套集合模型對插入更有耐性。Celko建議我們放棄每個節(jié)點(diǎn)總是有(rgt-lft+1)/2個孩子的特性。依我之見,這是一個朝前了半步的解決方案:在一個 帶有大 區(qū)間 和擴(kuò)展編號的嵌套集合模型 中的任何 區(qū)間 仍然可以覆蓋為增加更多的孩子而沒空間留下的 區(qū)間 。假如這些 區(qū)間 都允許僅在分散點(diǎn)有邊界(e.g.整數(shù))。那么其中需要使用一個密集的域來代替,像有理數(shù)或?qū)崝?shù)。
嵌套區(qū)間(Nested Intervals)
嵌套區(qū)間歸納為嵌套集合。一個節(jié)點(diǎn)[clft,crgt]是一個[plft,prgt]的(間接)后代,假如:
plft <= clft and crgt >= prgt
該域名的區(qū)間范圍不再僅限于整數(shù):如果需要,我們準(zhǔn)許有理數(shù)甚至實(shí)數(shù)。現(xiàn)在,一個合理的規(guī)則是,增加一個孩子節(jié)點(diǎn)不再是問題。一個類似規(guī)則的例子在父區(qū)間 [plft,prgt]里將找到一個空段[lft1,rgt1]并插入一個孩子節(jié)點(diǎn)[(2*lft1+rgt1)/3, (rgt1+2*lft)/3]:

在接下來的章節(jié)我們將改進(jìn)這一固有規(guī)則。
偏序(Partial Order)
讓我們看一下嵌套集合的二維圖。我們假定rgt為水平x軸,lft為垂直y軸。那么嵌套 區(qū)間 樹看起來像這樣:

每個節(jié)點(diǎn)[lft,rgt]在二維圓錐形里有它的子節(jié)點(diǎn)邊界y>=lft&x<=rgt。且右區(qū)間邊界總是小于左區(qū)間,所有節(jié)點(diǎn)均不允許超過對角線y=x。
另一種方式看這幅圖片應(yīng)注意父節(jié)點(diǎn)的子類中的一個孩子節(jié)點(diǎn),無論何時一系列定義在圓錐形孩子y>=clft&x<=crgt的所有點(diǎn)是父節(jié)點(diǎn)y>=plft&x<=prgt的一個子集。這個子集與平面上的圓錐形的關(guān)系是一個偏序。
現(xiàn)在我們知道遵照樹節(jié)點(diǎn)的兩個制約因素,我將確切地描述如何在xy平面上放置他們。
映射(The Mapping)
樹根的選擇完全是隨意的:我們假定根節(jié)點(diǎn)為區(qū)間[0,1]。在我們幾何圖案的解釋中,所有樹節(jié)點(diǎn)屬于xy平面上的正方形單元下部的三角形。
我們會通過歸納來進(jìn)一步詳細(xì)的描述映射 。對于每個樹節(jié)點(diǎn),讓我們首先在xy平面定義兩個重要的點(diǎn)。深度優(yōu)先會聚點(diǎn)是對角線與通過節(jié)點(diǎn)的垂直線之間的一個交叉點(diǎn)。例如,節(jié)點(diǎn)<x=1,y=1/2>的深度優(yōu)先會聚點(diǎn)為<x=1,y=1>。廣度優(yōu)先會聚點(diǎn)是對角線與通過這點(diǎn)的水平線之間的交叉點(diǎn)。例如, 點(diǎn)<x=1,y=1/2>的廣度優(yōu)先點(diǎn)為<x=1/2,y=1/2>。
現(xiàn)在,為每個父節(jié)點(diǎn),我們定義首個孩子的位置為一個父親點(diǎn)和深度優(yōu)先會聚點(diǎn)之間中點(diǎn)的一半。那么,每個兄弟節(jié)點(diǎn)被定義為一個前兄弟點(diǎn)和廣度優(yōu)先會聚點(diǎn)中點(diǎn)的一半:
規(guī)范化(Normalization)
接著,我們注意到x和y并沒有完全獨(dú)立。假如知道他們的和,就能知道x和y兩者是什么?給出有理數(shù)的分子和分母代表節(jié)點(diǎn)坐標(biāo)的和,我們能計(jì)算x和y坐標(biāo)追溯到:
我們會通過歸納來進(jìn)一步詳細(xì)的描述映射 。對于每個樹節(jié)點(diǎn),讓我們首先在xy平面定義兩個重要的點(diǎn)。深度優(yōu)先會聚點(diǎn)是對角線與通過節(jié)點(diǎn)的垂直線之間的一個交叉點(diǎn)。例如,節(jié)點(diǎn)<x=1,y=1/2>的深度優(yōu)先會聚點(diǎn)為<x=1,y=1>。廣度優(yōu)先會聚點(diǎn)是對角線與通過這點(diǎn)的水平線之間的交叉點(diǎn)。例如, 點(diǎn)<x=1,y=1/2>的廣度優(yōu)先點(diǎn)為<x=1/2,y=1/2>。
現(xiàn)在,為每個父節(jié)點(diǎn),我們定義首個孩子的位置為一個父親點(diǎn)和深度優(yōu)先會聚點(diǎn)之間中點(diǎn)的一半。那么,每個兄弟節(jié)點(diǎn)被定義為一個前兄弟點(diǎn)和廣度優(yōu)先會聚點(diǎn)中點(diǎn)的一半:

例如,節(jié)點(diǎn)2.1的位置在x=1/2, y=3/8。
現(xiàn)在映射定義了,很顯然我們正使用密集型域:它既不是有理數(shù),也不是實(shí)數(shù),而是一對分?jǐn)?shù)(當(dāng)然,盡管兩個前者已足夠)。
現(xiàn)在映射定義了,很顯然我們正使用密集型域:它既不是有理數(shù),也不是實(shí)數(shù),而是一對分?jǐn)?shù)(當(dāng)然,盡管兩個前者已足夠)。
有趣的是,父節(jié)點(diǎn)"1.2"的子樹后代是節(jié)點(diǎn)"1.1"子樹的一個向下縮小的復(fù)制品。 同樣的,節(jié)點(diǎn)1.1的子樹是節(jié)點(diǎn)"1."樹的一個向下縮小的復(fù)制品,一個帶自相似性的結(jié)構(gòu)被稱為分形圖。
規(guī)范化(Normalization)
接著,我們注意到x和y并沒有完全獨(dú)立。假如知道他們的和,就能知道x和y兩者是什么?給出有理數(shù)的分子和分母代表節(jié)點(diǎn)坐標(biāo)的和,我們能計(jì)算x和y坐標(biāo)追溯到:
function x_numer( numer integer, denom integer ) RETURN integer IS ret_num integer; ret_den integer; BEGIN ret_num := numer+1; ret_den := denom*2; while floor(ret_num/2) = ret_num/2 loop ret_num := ret_num/2; ret_den := ret_den/2; end loop; RETURN ret_num; END; function x_denom( numer integer, denom integer ) ... RETURN ret_den; END;?
當(dāng)然,y坐標(biāo)被定義為和的一個補(bǔ)數(shù):
function y_numer( numer integer, denom integer ) RETURN integer IS num integer; den integer; BEGIN num := x_numer(numer, denom); den := x_denom(numer, denom); while den < denom loop num := num*2; den := den*2; end loop; num := numer - num; while floor(num/2) = num/2 loop num := num/2; den := den/2; end loop; RETURN num; END; function y_denom( numer integer, denom integer ) ... RETURN den; END;?
現(xiàn)在測試(這里39/32的節(jié)點(diǎn)是1.3.1):
select x_number(39,32)||'/'||x_denom(39,32), y_number(39,32)||'/'||y_denom(39,32) from dual 5/8 19/32 select 5/8+19/32,39/32 from dual 1.21875 1.21875?
我不用一個浮數(shù)點(diǎn)來代表一個實(shí)數(shù),而所有函數(shù)用整數(shù)計(jì)算來替代。說穿了,是浮點(diǎn)數(shù)的一般概念,和IEEE標(biāo)準(zhǔn),尤其是僅對渲染3D游戲有益。在最后的測試中,盡管我們使用一個浮點(diǎn)來驗(yàn)證5/8和19/32,通過前查詢的返回,證明確實(shí)增加到了39/32。
我們將存儲這兩個整數(shù)--分子和分母的x和y坐標(biāo)和--做為一個編碼節(jié)點(diǎn)路徑。碰巧,Celko的嵌套集合也是是兩個整數(shù)。不像嵌套集合,我們的映射是穩(wěn)定的:每個節(jié)點(diǎn)在xy平面有一個預(yù)定義的位置,在涉及節(jié)點(diǎn)位置的層次查詢時不需引用數(shù)據(jù)庫便能回應(yīng)。在這方面,我們的分層模型本質(zhì)上是一個有理數(shù)編碼的原型路徑。
查找父編碼和兄弟編號
給一個 numer/denom編碼的孩子節(jié)點(diǎn),我們可以這樣找它的父節(jié)點(diǎn):
function parent_numer( numer integer, denom integer ) RETURN integer IS ret_num integer; ret_den integer; BEGIN if numer=3 then return NULL; end if; ret_num := (numer-1)/2; ret_den := denom/2; while floor((ret_num-1)/4) = (ret_num-1)/4 loop ret_num := (ret_num+1)/2; ret_den := ret_den/2; end loop; RETURN ret_num; END; function parent_denom( numer integer, denom integer ) ... RETURN ret_den; END;?
背后的算法如下:假如節(jié)點(diǎn)已是最頂層--則所有這些節(jié)點(diǎn)有一個分子等于3--且節(jié)點(diǎn)沒有父親。否則,我們須垂直下移xy平面到跟深度優(yōu)先會聚點(diǎn)相等的距離。如果節(jié)點(diǎn)正好是第一個孩子,那么這就是回應(yīng)。否則我們須平移到跟廣度優(yōu)先會聚點(diǎn)相等的距離直到見到父節(jié)點(diǎn)。
這里是測試方法(在這27/32的節(jié)點(diǎn)是2.1.2,當(dāng)7/8是2.1時);
select parent_numer(27,32)||'/'||parent_denom(27,32) from dual 7/8?
在前面的方法,當(dāng)橫向?qū)Ш綍r節(jié)將得到兄弟編號的計(jì)算步驟:
function sibling_number( numer integer, denom integer ) RETURN integer IS ret_num integer; ret_den integer; ret integer; BEGIN if numer=3 then return NULL; end if; ret_num := (numer-1)/2; ret_den := denom/2; ret := 1; while floor((ret_num-1)/4) = (ret_num-1)/4 loop if ret_num=1 and ret_den=1 then return ret; end if; ret_num := (ret_num+1)/2; ret_den := ret_den/2; ret := ret+1; end loop; RETURN ret; END;?
一個節(jié)點(diǎn)在最近的一級的一個特殊停止條件,ret_num=1和ret_den=1是必須的。
那測試:
select sibling_number(7,8) from dual; 1?
計(jì)算具體路徑和節(jié)點(diǎn)間的距離
嚴(yán)格來講,我們沒有使用具體路徑,由于我們的編碼是可選擇的。另一方面,一個具體路徑在 層次結(jié)構(gòu)上 能提供 一個更直覺的節(jié)點(diǎn)位置,這樣我們能使用具體路徑為數(shù)據(jù)輸入/出,假如我們提供映射到我們的模型。
從上節(jié)來看實(shí)現(xiàn)是一個簡單的方法應(yīng)用。我們打印兄弟編號,跳到父節(jié)點(diǎn),然后重復(fù)以上兩步直到根部為止。
function path( numer integer, denom integer ) RETURN varchar2 IS BEGIN if numer is NULL then return ''; end if; RETURN path(parent_numer(numer, denom), parent_denom(numer, denom)) || '.' || sibling_number(numer, denom); END; select path(15,16) from dual .2.1.1?
現(xiàn)在我們準(zhǔn)備來寫主要的查詢:給2個節(jié)點(diǎn),P和C,何時節(jié)P是C的父節(jié)點(diǎn)? 如果從P可達(dá)C, 一個很普通的查詢將返回P和C之間的居次編號和一些異常提示;反之:
function distance( num1 integer, den1 integer, num2 integer, den2 integer ) RETURN integer IS BEGIN if num1 is NULL then return -999999; end if; if num1=num2 and den1=den2 then return 0; end if; RETURN 1+distance(parent_numer(num1, den1), parent_denom(num1, den1), num2,den2); END; select distance(27,32,3,4) from dual 2
負(fù)數(shù)字都作為異常來處理。假如節(jié)點(diǎn)num1/den1從num2/den2不可達(dá),那么將導(dǎo)向回根部,且將返回層次(num1/den1)-999999(讀者建議找個更得體的解釋)。
可選擇方式來回答是否通過簡單計(jì)算x和y坐標(biāo)來連接兩個節(jié)點(diǎn),然后檢查是否父節(jié)點(diǎn)閉區(qū)間于孩子。盡管沒有涉及磁盤方法,檢查是否偏序的節(jié)點(diǎn)間存在似乎更小代價。另一方面,它僅是一個比較兩個整數(shù)是否為原子操作的人工打造的計(jì)算機(jī)體系結(jié)構(gòu)。該方法的更完美的實(shí)現(xiàn)將包含一個無限區(qū)間的整數(shù)域(這些類型的數(shù)字是計(jì)算機(jī)系統(tǒng)所支持的),那么一個比較操作也將得循環(huán)。
我們的系統(tǒng)不會完全沒有一個路徑的反向函數(shù),一當(dāng)提供路徑時,它會返回一個節(jié)點(diǎn)numer/denom的值。讓我們介紹兩個輔助函數(shù),首先:
function child_numer ( num integer, den integer, child integer ) RETURN integer IS BEGIN RETURN num*power(2, child)+3-power(2, child); END; function child_denom ( num integer, den integer, child integer ) RETURN integer IS BEGIN RETURN den*power(2, child); END; select child_numer(3,2,3) || '/' || child_denom(3,2,3) from dual 19/16?
例如,節(jié)點(diǎn)1(編碼為3/2)的第三個孩子節(jié)點(diǎn)節(jié)點(diǎn)是1.3(編碼為19/16)。
路徑編碼函數(shù)是:
function path_numer( path varchar2 ) RETURN integer IS num integer; den integer; postfix varchar2(1000); sibling varchar2(100); BEGIN num := 1; den := 1; postfix := '.' || path || '.'; while length(postfix) > 1 loop sibling := substr(postfix, 2, instr(postfix,'.',2)-2); postfix := substr(postfix, instr(postfix,'.',2), length(postfix) -instr(postfix,'.',2)+1); num := child_numer(num,den,to_number(sibling)); den := child_denom(num,den,to_number(sibling)); end loop; RETURN num; END; function path_denom( path varchar2 ) ... RETURN den; END; select path_numer('2.1.3') || '/' || path_denom('2.1.3') from dual 51/64
最后測試
現(xiàn)在基礎(chǔ)架構(gòu)已完成,我們可以測試它,讓我們創(chuàng)建一個層次結(jié)構(gòu)
create table emps ( name varchar2(30), numer integer, denom integer ) alter table emps ADD CONSTRAINT uk_name UNIQUE (name) USING INDEX (CREATE UNIQUE INDEX name_idx on emps(name)) ADD CONSTRAINT UK_node UNIQUE (numer, denom) USING INDEX (CREATE UNIQUE INDEX node_idx on emps(numer, denom))
然后填入一些數(shù)據(jù):
insert into emps values ('KING', path_numer('1'),path_denom('1')); insert into emps values ('JONES', path_numer('1.1'),path_denom('1.1')); insert into emps values ('SCOTT', path_numer('1.1.1'),path_denom('1.1.1')); insert into emps values ('ADAMS', path_numer('1.1.1.1'),path_denom('1.1.1.1')); insert into emps values ('FORD', path_numer('1.1.2'),path_denom('1.1.2')); insert into emps values ('SMITH', path_numer('1.1.2.1'),path_denom('1.1.2.1')); insert into emps values ('BLAKE', path_numer('1.2'),path_denom('1.2')); insert into emps values ('ALLEN', path_numer('1.2.1'),path_denom('1.2.1')); insert into emps values ('WARD', path_numer('1.2.2'),path_denom('1.2.2')); insert into emps values ('MARTIN', path_numer('1.2.3'),path_denom('1.2.3')); insert into emps values ('TURNER', path_numer('1.2.4'),path_denom('1.2.4')); insert into emps values ('CLARK', path_numer('1.3'),path_denom('1.3')); insert into emps values ('MILLER', path_numer('1.3.1'),path_denom('1.3.1')); commit;?
所有函數(shù)在前節(jié)已編寫可方便地連接到一個單獨(dú)的視圖中:
create or replace view hierarchy as select name, numer, denom, y_numer(numer,denom) numer_left, y_denom(numer,denom) denom_left, x_numer(numer,denom) numer_right, x_denom(numer,denom) denom_right, path (numer,denom) path, distance(numer,denom,3,2) depth from emps?
最后,我們創(chuàng)建一個分層報告
- 深度優(yōu)先枚舉,按左區(qū)間排序
select lpad(' ',3*depth)||name from hierarchy order by numer_left/denom_left LPAD('',3*DEPTH)||NAME ----------------------------------------------- KING CLARK MILLER BLAKE TURNER MARTIN WARD ALLEN JONES FORD SMITH?
- 廣度優(yōu)先枚舉,按右區(qū)間排序
select lpad(' ',3*depth)||name from hierarchy order by numer_right/denom_right desc LPAD('',3*DEPTH)||NAME ----------------------------------------------------- KING JONES SCOTT ADAMS FORD SMITH BLAKE ALLEN WARD MARTIN TURNER CLARK MILLER?
- 深度優(yōu)先枚舉,按路徑排序(輸出同#2)
select lpad(' ',3*depth)||name from hierarchy order by path LPAD('',3*DEPTH)||NAME ----------------------------------------------------- KING JONES SCOTT ADAMS FORD SMITH BLAKE ALLEN WARD MARTIN TURNER CLARK MILLER?
- JONES的所有孩子, 不 包括自己
select h1.name from hierarchy h1, hierarchy h2 where h2.name = 'JONES' and distance(h1.numer, h1.denom, h2.numer, h2.denom)>0; NAME ------------------------------ SCOTT ADAMS FORD SMITH?
- FORD的所有祖先,不包含自己
select h2.name from hierarchy h1, hierarchy h2 where h1.name = 'FORD' and distance(h1.numer, h1.denom, h2.numer, h2.denom)>0; NAME ------------------------------ KING JONES?
關(guān)于本文作者
Vadim Tropashko 工作于Orace公司的Real World Performance組。在以往的生活,他是應(yīng)用程序員,并曾把B.Stroustrup的《C++編程語言》第二版譯成俄文。他當(dāng)前興趣包括SQL優(yōu)化,數(shù)據(jù)庫約束和計(jì)算機(jī)代數(shù)學(xué)系統(tǒng)。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
