黄色网页视频 I 影音先锋日日狠狠久久 I 秋霞午夜毛片 I 秋霞一二三区 I 国产成人片无码视频 I 国产 精品 自在自线 I av免费观看网站 I 日本精品久久久久中文字幕5 I 91看视频 I 看全色黄大色黄女片18 I 精品不卡一区 I 亚洲最新精品 I 欧美 激情 在线 I 人妻少妇精品久久 I 国产99视频精品免费专区 I 欧美影院 I 欧美精品在欧美一区二区少妇 I av大片网站 I 国产精品黄色片 I 888久久 I 狠狠干最新 I 看看黄色一级片 I 黄色精品久久 I 三级av在线 I 69色综合 I 国产日韩欧美91 I 亚洲精品偷拍 I 激情小说亚洲图片 I 久久国产视频精品 I 国产综合精品一区二区三区 I 色婷婷国产 I 最新成人av在线 I 国产私拍精品 I 日韩成人影音 I 日日夜夜天天综合

Trees in SQL: Nested Sets and Materizlized P

系統(tǒng) 2090 0
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)的路徑名。

應(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]:

插入之后,我們?nèi)耘f有兩有空段[lft1,(2*lft1+rgt1)/3]和[(rgt1+2*lft)/3,rgt1]來(lái)增加更多的孩子到父節(jié)點(diǎn)。

在接下來(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)的一半:

例如,節(jié)點(diǎn)2.1的位置在x=1/2, y=3/8。

現(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;
      
?
在這邊函數(shù)x_denom主體與x_numer的不同僅在返回變量。通俗地說(shuō),number+1的遞增將ret_num/ret_den點(diǎn)垂直地向?qū)蔷€方向移動(dòng),且x的坐標(biāo)是這個(gè)值的一半,所以我們僅能讓分母乘2。接著,分子和分母我們均減少相同的二次冪。

當(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)。


Trees in SQL: Nested Sets and Materizlized Path


更多文章、技術(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ì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論