by Vadim Tropashko
?
翻譯:
Janwer Zhang
關(guān)系數(shù)據(jù)庫(kù)通常被認(rèn)為是在其先輩網(wǎng)絡(luò)和分層模型上的進(jìn)步發(fā)展。在每個(gè)層級(jí)查詢(xún)方面,當(dāng)模型轉(zhuǎn)換成依賴(lài)關(guān)系時(shí),他們結(jié)果是驚人地不完整。幾乎每?jī)扇齻€(gè)月總有關(guān)于如何在數(shù)據(jù)庫(kù)中建立樹(shù)模型的問(wèn)題彈出在comp.database.theory新聞組。在本文中我將探討兩者用四個(gè)眾所周知的方法的實(shí)現(xiàn),并展示它們之間的關(guān)聯(lián)。我們將找到一個(gè)可以被看作是具體路徑(materialized path)和嵌套集合(nested sets)“混合式”的新方法。
鏈接表(Adjacency List)
樹(shù)結(jié)構(gòu)是有向無(wú)環(huán)圖(Directed Acyclic Graph, 簡(jiǎn)稱(chēng)DAG)的一個(gè)特殊案例。描繪DAG結(jié)構(gòu)的方式之一:
create table emp (
ename varchar2(100),
mgrname varchar2(100
);
emp 表的每條記錄通過(guò)指向上級(jí)mgrname的ename來(lái)標(biāo)識(shí)。例如,假如JONES向KING報(bào)告,于是emp表中含有<ename='JONES', mgrname='KING'>的記錄。假設(shè),emp表也包含<ename='SCOTT', mgrname='JONES'>。此外,假如emp表不含有<ename='SCOTT',mgrname='KING'>記錄,對(duì)于其它每對(duì)毗連的記錄也是如此,那么它就是所謂的鄰接表(Adjacency List)。如果正好相反,那emp表是可傳遞的閉包關(guān)系。
一個(gè)典型的層次查詢(xún)可能會(huì)詢(xún)問(wèn)SCOTT是否間接向KING報(bào)告。由于我們不知道兩者間的層級(jí)數(shù)字,因此我們不能告訴emp表要進(jìn)行多少次自連接,以至于這個(gè)任務(wù)不能用傳統(tǒng)的SQL解決。假如知道emp表是傳遞閉包tcemp,那么這個(gè)查詢(xún)是小事一樁:
select 'TRUE' from tcemp where ename='SCOTT' and mgrname='KING'
這個(gè)簡(jiǎn)便查詢(xún)的犧牲代價(jià)
transitive closure maintenance
.
此外,SQL擴(kuò)展:SQL3/DB2遞歸查詢(xún),能執(zhí)行層次查詢(xún):
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';
這個(gè)tcemp計(jì)算作為中間關(guān)聯(lián),或采用Oracle專(zhuān)有連接的語(yǔ)法:
select 'TRUE' from ( select ename from emp connect by prior mgrname = ename
start with ename = 'SCOTT') where ename = 'KING';
其中內(nèi)查詢(xún)"chases the pointers"從SCOTT節(jié)點(diǎn)到樹(shù)的根節(jié)點(diǎn),而外查詢(xún)檢查KING節(jié)點(diǎn)是否在路徑上。
鏈接表可以說(shuō)是最直觀的樹(shù)模型。這是我們的主要焦點(diǎn),不過(guò),接下來(lái)還有兩種方法。
具體化路徑(Materialized Path)
在這種做法中每條記錄存儲(chǔ)到根部為止的整個(gè)路徑。在我們前面的例子中,讓我們假定KING為根節(jié)點(diǎn),然后,記錄 ename="SCOTT" 通過(guò)路徑 SCOTT->JONES->KING 連接到根部。現(xiàn)代數(shù)據(jù)庫(kù)允許描繪一個(gè)節(jié)點(diǎn)清單作為一個(gè)單一的值,但由于具體路徑在被發(fā)明之前的長(zhǎng)時(shí)間里,約定停留在經(jīng)由一些分隔符連接的普通字符串節(jié)點(diǎn),最常見(jiàn)的'.'或'/。在后一種情況下,尤其明顯一個(gè)類(lèi)似UNIX文件系統(tǒng)的路徑名。
在這種做法中每條記錄存儲(chǔ)到根部為止的整個(gè)路徑。在我們前面的例子中,讓我們假定KING為根節(jié)點(diǎn),然后,記錄 ename="SCOTT" 通過(guò)路徑 SCOTT->JONES->KING 連接到根部。現(xiàn)代數(shù)據(jù)庫(kù)允許描繪一個(gè)節(jié)點(diǎn)清單作為一個(gè)單一的值,但由于具體路徑在被發(fā)明之前的長(zhǎng)時(shí)間里,約定停留在經(jīng)由一些分隔符連接的普通字符串節(jié)點(diǎn),最常見(jiàn)的'.'或'/。在后一種情況下,尤其明顯一個(gè)類(lèi)似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的第二個(gè)孩子節(jié)點(diǎn)
讓我們寫(xiě)一些查詢(xún)。
1. 雇員FORD和它的一系列上級(jí)節(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'
?
盡管兩個(gè)查詢(xún)看起來(lái)是對(duì)稱(chēng)的,但在它們的各自的執(zhí)行中有根本性的差別。如果一顆子樹(shù)的下級(jí)節(jié)點(diǎn)相比整體層次大小而言是較小的,那么在數(shù)據(jù)庫(kù)中通過(guò)執(zhí)行主鍵抓取e2記錄,然后執(zhí)行e1.path范圍的掃描,這是快速的保證。
在另一方面,上級(jí)節(jié)點(diǎn)的查詢(xún)大體上是相同的
select e1.ename from emp e1, emp e2 where e2.path > e1.path and e2.path < e1.path || 'Z'
and e2.ename='FORD'
?
或者,本來(lái)知道e2.path,請(qǐng)注意,它可以進(jìn)一小減少到:
select e1.ename from emp e1 where e2.path>e1.path and e2.path<e1.path || 'Z'
?
這里,很顯然path上的索引不會(huì)起作用(除了e2.path恰好是靠近域邊界的意外情況下,以便有選擇性的判定e2.path>e1.path)。明顯的解決方法是,我們并沒(méi)有利用數(shù)據(jù)庫(kù)去計(jì)算出所有的上級(jí)路徑!例如,1.1.2的上級(jí)是1.1和1。一個(gè)簡(jiǎn)單的遞歸字符串解析函數(shù)可以提取這些路徑,那么回答上層的名稱(chēng)通過(guò):
select e1.ename from emp where e1.path in ('1.1','1')
這應(yīng)該是個(gè)快速級(jí)聯(lián)的執(zhí)行方案。
嵌套集合(Nested Sets)
具體路徑和
Joe Celko的嵌套集合
均具有標(biāo)準(zhǔn)SQL語(yǔ)法層次查詢(xún)的回應(yīng)能力。在兩種模型中,節(jié)點(diǎn)的全局位置在層次中是“編碼”的,相反鏈接表的每個(gè)連接僅是一個(gè)近鄰間的局部連接。類(lèi)似于具體路徑,嵌套集合模型也遭遇上層節(jié)點(diǎn)查詢(xún)的性能問(wèn)題。
select p2.emp from Personnel p1, Personnel p2where p1.lft between p2.lft and
p2.rgtand p1.emp = 'Chuck'
(注意:這個(gè)查詢(xún)借自
previously cited Celko article
)
此處,這問(wèn)題變得比具體路徑情況下更明確:我們需要找出特定點(diǎn)的所有間隔。這個(gè)問(wèn)題很難解決。盡管有像R-Tree的專(zhuān)門(mén)索引表,但它們中沒(méi)有一個(gè)能像B- Tree一樣被普遍接受。例如,如果頂層路徑僅包含10個(gè)節(jié)點(diǎn),而整棵樹(shù)的大小為1000000,那么沒(méi)有一種索引技術(shù)能夠提供 1000000/10=100000倍的性能提升。(這樣的性能改進(jìn)因素通常與索引掃描范圍類(lèi)似,非常有選擇性,以數(shù)據(jù)卷為條件的典型關(guān)聯(lián)。)
不像一個(gè)具體路徑,這邊的技巧是我們 計(jì)算所有節(jié)點(diǎn)而不須為查詢(xún)數(shù)據(jù)庫(kù)做不必要的工作。
另一個(gè)--較根本性的--嵌套集合的缺點(diǎn)是嵌套集編碼是暫時(shí)性的。如果我們?cè)诜謱咏Y(jié)構(gòu)中間插入一個(gè) 節(jié)點(diǎn),插入點(diǎn)邊界以上的所有間隔必須重新計(jì)算。換句話(huà)說(shuō),當(dāng)我們插入一條記錄到數(shù)據(jù)庫(kù)中,大概有一半左右的其它記錄需要被更新。這也是為什么嵌套集合模型僅能接收有限的靜態(tài)層次。
嵌套集合間的區(qū)間為整數(shù)。為嘗試使嵌套集合模型對(duì)插入更有耐性。Celko建議我們放棄每個(gè)節(jié)點(diǎn)總是有(rgt-lft+1)/2個(gè)孩子的特性。依我之見(jiàn),這是一個(gè)朝前了半步的解決方案:在一個(gè) 帶有大 區(qū)間 和擴(kuò)展編號(hào)的嵌套集合模型 中的任何 區(qū)間 仍然可以覆蓋為增加更多的孩子而沒(méi)空間留下的 區(qū)間 。假如這些 區(qū)間 都允許僅在分散點(diǎn)有邊界(e.g.整數(shù))。那么其中需要使用一個(gè)密集的域來(lái)代替,像有理數(shù)或?qū)崝?shù)。
嵌套區(qū)間(Nested Intervals)
嵌套區(qū)間歸納為嵌套集合。一個(gè)節(jié)點(diǎn)[clft,crgt]是一個(gè)[plft,prgt]的(間接)后代,假如:
plft <= clft and crgt >= prgt
該域名的區(qū)間范圍不再僅限于整數(shù):如果需要,我們準(zhǔn)許有理數(shù)甚至實(shí)數(shù)。現(xiàn)在,一個(gè)合理的規(guī)則是,增加一個(gè)孩子節(jié)點(diǎn)不再是問(wèn)題。一個(gè)類(lèi)似規(guī)則的例子在父區(qū)間 [plft,prgt]里將找到一個(gè)空段[lft1,rgt1]并插入一個(gè)孩子節(jié)點(diǎn)[(2*lft1+rgt1)/3, (rgt1+2*lft)/3]:
在接下來(lái)的章節(jié)我們將改進(jìn)這一固有規(guī)則。
偏序(Partial Order)
讓我們看一下嵌套集合的二維圖。我們假定rgt為水平x軸,lft為垂直y軸。那么嵌套 區(qū)間 樹(shù)看起來(lái)像這樣:
每個(gè)節(jié)點(diǎn)[lft,rgt]在二維圓錐形里有它的子節(jié)點(diǎn)邊界y>=lft&x<=rgt。且右區(qū)間邊界總是小于左區(qū)間,所有節(jié)點(diǎn)均不允許超過(guò)對(duì)角線y=x。
另一種方式看這幅圖片應(yīng)注意父節(jié)點(diǎn)的子類(lèi)中的一個(gè)孩子節(jié)點(diǎn),無(wú)論何時(shí)一系列定義在圓錐形孩子y>=clft&x<=crgt的所有點(diǎn)是父節(jié)點(diǎn)y>=plft&x<=prgt的一個(gè)子集。這個(gè)子集與平面上的圓錐形的關(guān)系是一個(gè)偏序。
現(xiàn)在我們知道遵照樹(shù)節(jié)點(diǎn)的兩個(gè)制約因素,我將確切地描述如何在xy平面上放置他們。
映射(The Mapping)
樹(shù)根的選擇完全是隨意的:我們假定根節(jié)點(diǎn)為區(qū)間[0,1]。在我們幾何圖案的解釋中,所有樹(shù)節(jié)點(diǎn)屬于xy平面上的正方形單元下部的三角形。
我們會(huì)通過(guò)歸納來(lái)進(jìn)一步詳細(xì)的描述映射 。對(duì)于每個(gè)樹(shù)節(jié)點(diǎn),讓我們首先在xy平面定義兩個(gè)重要的點(diǎn)。深度優(yōu)先會(huì)聚點(diǎn)是對(duì)角線與通過(guò)節(jié)點(diǎn)的垂直線之間的一個(gè)交叉點(diǎn)。例如,節(jié)點(diǎn)<x=1,y=1/2>的深度優(yōu)先會(huì)聚點(diǎn)為<x=1,y=1>。廣度優(yōu)先會(huì)聚點(diǎn)是對(duì)角線與通過(guò)這點(diǎn)的水平線之間的交叉點(diǎn)。例如, 點(diǎn)<x=1,y=1/2>的廣度優(yōu)先點(diǎn)為<x=1/2,y=1/2>。
現(xiàn)在,為每個(gè)父節(jié)點(diǎn),我們定義首個(gè)孩子的位置為一個(gè)父親點(diǎn)和深度優(yōu)先會(huì)聚點(diǎn)之間中點(diǎn)的一半。那么,每個(gè)兄弟節(jié)點(diǎn)被定義為一個(gè)前兄弟點(diǎn)和廣度優(yōu)先會(huì)聚點(diǎn)中點(diǎn)的一半:
規(guī)范化(Normalization)
接著,我們注意到x和y并沒(méi)有完全獨(dú)立。假如知道他們的和,就能知道x和y兩者是什么?給出有理數(shù)的分子和分母代表節(jié)點(diǎn)坐標(biāo)的和,我們能計(jì)算x和y坐標(biāo)追溯到:
我們會(huì)通過(guò)歸納來(lái)進(jìn)一步詳細(xì)的描述映射 。對(duì)于每個(gè)樹(shù)節(jié)點(diǎn),讓我們首先在xy平面定義兩個(gè)重要的點(diǎn)。深度優(yōu)先會(huì)聚點(diǎn)是對(duì)角線與通過(guò)節(jié)點(diǎn)的垂直線之間的一個(gè)交叉點(diǎn)。例如,節(jié)點(diǎn)<x=1,y=1/2>的深度優(yōu)先會(huì)聚點(diǎn)為<x=1,y=1>。廣度優(yōu)先會(huì)聚點(diǎn)是對(duì)角線與通過(guò)這點(diǎn)的水平線之間的交叉點(diǎn)。例如, 點(diǎn)<x=1,y=1/2>的廣度優(yōu)先點(diǎn)為<x=1/2,y=1/2>。
現(xiàn)在,為每個(gè)父節(jié)點(diǎn),我們定義首個(gè)孩子的位置為一個(gè)父親點(diǎn)和深度優(yōu)先會(huì)聚點(diǎn)之間中點(diǎn)的一半。那么,每個(gè)兄弟節(jié)點(diǎn)被定義為一個(gè)前兄弟點(diǎn)和廣度優(yōu)先會(huì)聚點(diǎn)中點(diǎn)的一半:
例如,節(jié)點(diǎn)2.1的位置在x=1/2, y=3/8。
現(xiàn)在映射定義了,很顯然我們正使用密集型域:它既不是有理數(shù),也不是實(shí)數(shù),而是一對(duì)分?jǐn)?shù)(當(dāng)然,盡管兩個(gè)前者已足夠)。
現(xiàn)在映射定義了,很顯然我們正使用密集型域:它既不是有理數(shù),也不是實(shí)數(shù),而是一對(duì)分?jǐn)?shù)(當(dāng)然,盡管兩個(gè)前者已足夠)。
有趣的是,父節(jié)點(diǎn)"1.2"的子樹(shù)后代是節(jié)點(diǎn)"1.1"子樹(shù)的一個(gè)向下縮小的復(fù)制品。 同樣的,節(jié)點(diǎn)1.1的子樹(shù)是節(jié)點(diǎn)"1."樹(shù)的一個(gè)向下縮小的復(fù)制品,一個(gè)帶自相似性的結(jié)構(gòu)被稱(chēng)為分形圖。
規(guī)范化(Normalization)
接著,我們注意到x和y并沒(méi)有完全獨(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)被定義為和的一個(gè)補(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)在測(cè)試(這里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
?
我不用一個(gè)浮數(shù)點(diǎn)來(lái)代表一個(gè)實(shí)數(shù),而所有函數(shù)用整數(shù)計(jì)算來(lái)替代。說(shuō)穿了,是浮點(diǎn)數(shù)的一般概念,和IEEE標(biāo)準(zhǔn),尤其是僅對(duì)渲染3D游戲有益。在最后的測(cè)試中,盡管我們使用一個(gè)浮點(diǎn)來(lái)驗(yàn)證5/8和19/32,通過(guò)前查詢(xún)的返回,證明確實(shí)增加到了39/32。
我們將存儲(chǔ)這兩個(gè)整數(shù)--分子和分母的x和y坐標(biāo)和--做為一個(gè)編碼節(jié)點(diǎn)路徑。碰巧,Celko的嵌套集合也是是兩個(gè)整數(shù)。不像嵌套集合,我們的映射是穩(wěn)定的:每個(gè)節(jié)點(diǎn)在xy平面有一個(gè)預(yù)定義的位置,在涉及節(jié)點(diǎn)位置的層次查詢(xún)時(shí)不需引用數(shù)據(jù)庫(kù)便能回應(yīng)。在這方面,我們的分層模型本質(zhì)上是一個(gè)有理數(shù)編碼的原型路徑。
查找父編碼和兄弟編號(hào)
給一個(gè) 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)有一個(gè)分子等于3--且節(jié)點(diǎn)沒(méi)有父親。否則,我們須垂直下移xy平面到跟深度優(yōu)先會(huì)聚點(diǎn)相等的距離。如果節(jié)點(diǎn)正好是第一個(gè)孩子,那么這就是回應(yīng)。否則我們須平移到跟廣度優(yōu)先會(huì)聚點(diǎn)相等的距離直到見(jiàn)到父節(jié)點(diǎn)。
這里是測(cè)試方法(在這27/32的節(jié)點(diǎn)是2.1.2,當(dāng)7/8是2.1時(shí));
select parent_numer(27,32)||'/'||parent_denom(27,32) from dual
7/8
?
在前面的方法,當(dāng)橫向?qū)Ш綍r(shí)節(jié)將得到兄弟編號(hào)的計(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;
?
一個(gè)節(jié)點(diǎn)在最近的一級(jí)的一個(gè)特殊停止條件,ret_num=1和ret_den=1是必須的。
那測(cè)試:
select sibling_number(7,8) from dual;
1
?
計(jì)算具體路徑和節(jié)點(diǎn)間的距離
嚴(yán)格來(lái)講,我們沒(méi)有使用具體路徑,由于我們的編碼是可選擇的。另一方面,一個(gè)具體路徑在 層次結(jié)構(gòu)上 能提供 一個(gè)更直覺(jué)的節(jié)點(diǎn)位置,這樣我們能使用具體路徑為數(shù)據(jù)輸入/出,假如我們提供映射到我們的模型。
從上節(jié)來(lái)看實(shí)現(xiàn)是一個(gè)簡(jiǎn)單的方法應(yīng)用。我們打印兄弟編號(hào),跳到父節(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)備來(lái)寫(xiě)主要的查詢(xún):給2個(gè)節(jié)點(diǎn),P和C,何時(shí)節(jié)P是C的父節(jié)點(diǎn)? 如果從P可達(dá)C, 一個(gè)很普通的查詢(xún)將返回P和C之間的居次編號(hào)和一些異常提示;反之:
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ù)字都作為異常來(lái)處理。假如節(jié)點(diǎn)num1/den1從num2/den2不可達(dá),那么將導(dǎo)向回根部,且將返回層次(num1/den1)-999999(讀者建議找個(gè)更得體的解釋?zhuān)?
可選擇方式來(lái)回答是否通過(guò)簡(jiǎn)單計(jì)算x和y坐標(biāo)來(lái)連接兩個(gè)節(jié)點(diǎn),然后檢查是否父節(jié)點(diǎn)閉區(qū)間于孩子。盡管沒(méi)有涉及磁盤(pán)方法,檢查是否偏序的節(jié)點(diǎn)間存在似乎更小代價(jià)。另一方面,它僅是一個(gè)比較兩個(gè)整數(shù)是否為原子操作的人工打造的計(jì)算機(jī)體系結(jié)構(gòu)。該方法的更完美的實(shí)現(xiàn)將包含一個(gè)無(wú)限區(qū)間的整數(shù)域(這些類(lèi)型的數(shù)字是計(jì)算機(jī)系統(tǒng)所支持的),那么一個(gè)比較操作也將得循環(huán)。
我們的系統(tǒng)不會(huì)完全沒(méi)有一個(gè)路徑的反向函數(shù),一當(dāng)提供路徑時(shí),它會(huì)返回一個(gè)節(jié)點(diǎn)numer/denom的值。讓我們介紹兩個(gè)輔助函數(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)的第三個(gè)孩子節(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
最后測(cè)試
現(xiàn)在基礎(chǔ)架構(gòu)已完成,我們可以測(cè)試它,讓我們創(chuàng)建一個(gè)層次結(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é)已編寫(xiě)可方便地連接到一個(gè)單獨(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)建一個(gè)分層報(bào)告
- 深度優(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++編程語(yǔ)言》第二版譯成俄文。他當(dāng)前興趣包括SQL優(yōu)化,數(shù)據(jù)庫(kù)約束和計(jì)算機(jī)代數(shù)學(xué)系統(tǒng)。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

