Decode360's Blog

          業精于勤而荒于嬉 QQ:150355677 MSN:decode360@hotmail.com

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 ::  :: 管理 ::
            397 隨筆 :: 33 文章 :: 29 評論 :: 0 Trackbacks
          Connect By的簡單運用
          ?
          ?
          一、首先從一個經典的查詢序列數例子入手:
          ?
          ??? select level? from dual connect by level <=10
          ??? select level? from dual connect by 1 = 1
          ??? 上例均可查詢得到1 .. N 的序列
          ?
          ??? 我們來分析一下其工作原理,level<=10用來控制循環的次數,即要重復多少次掃描表dual中的內容。第一次掃描得出的結果集的level都是1,第二次掃描的結果集的level都是2,依此類推。可能用文字描述的不太容易懂,下面我們通過試驗來說明:
          ??? with x as
          ??? (select 'aa' chr from dual
          ??? union all
          ??? select 'bb' chr from dual)
          ??? select level,chr,lpad(' ',(level-1)*5,'-')||chr other from x connect by level<=3;
          ?
          ??? LEVEL CHR??? OTHER
          ??? ----- ------ ----------------------
          ??? 1???? aa???? aa
          ??? 2???? aa???? ---- aa
          ??? 3???? aa???? --------- aa
          ??? 3???? bb???? --------- bb
          ??? 2???? bb???? ---- bb
          ??? 3???? aa???? --------- aa
          ??? 3???? bb???? --------- bb
          ??? 1???? bb???? bb
          ??? 2???? aa???? ---- aa
          ??? 3???? aa???? --------- aa
          ??? 3???? bb???? --------- bb
          ??? 2???? bb???? ---- bb
          ??? 3???? aa???? --------- aa
          ??? 3???? bb???? --------- bb
          ?
          ??? 可見是全部level的樹形結構,當掃描對象是dual時,即一個level只生成一條記錄.
          ?
          ?
          二、如何解決from dual只顯示100行的問題:

          ??? select level? from dual connect by level <=300??
          ??? 上面這條語句在各個不同的Oracle版本、不同的client下面,顯示都是不同的。例如對于9i,在PLSQL DEV下只顯示100行,100行以上的不顯示。在SQLPlus下只顯示1行,其余不顯示。對于10g則無論在哪都完全顯示。
          ??? 對于這個問題的解決,使用以下方法即可:

          ??? select * from (level? from dual connect by level <=300);
          ?
          ??? 應用舉例:
          ??? select to_date(2008||'0101','yyyymmdd') + rownum - 1 rq,
          ??? to_char(to_date(2008||'0101','yyyymmdd') + rownum - 1,'day') day
          ??? from(select rownum from dual
          ??? connect by rownum <= to_date(2008||'1231','yyyymmdd') - to_date(2008||'0101','yyyymmdd')+1);
          ??? 作用:列出所有日期及星期幾,可用于查詢工作日
          ?
          ?
          三、start with ... connect by 用法講解:

          ??? 構建如下table:

          ??? ID?? NAME? PID
          ??? ---- ----- ----
          ??? 1??? 10??? 0
          ??? 2??? 11??? 1
          ??? 3??? 20??? 0
          ??? 4??? 12??? 1
          ??? 5??? 121?? 2
          ??? code example1:
          ??? select TBL_TEST.*,level from TBL_TEST
          ??? start with pid=1 --可寫到connect by后面
          ??? connect by prior pid = id
          ?
          ??? ID?? NAME? PID? LEVEL
          ??? ---- ----- ---- ------
          ??? 2??? 11??? 1??? 1
          ??? 1??? 10??? 0??? 2
          ??? 4??? 12??? 1??? 1
          ??? 1??? 10??? 0??? 2
          ?
          ??? code example2:
          ?
          ??? select TBL_TEST.*,level from TBL_TEST
          ??? start with id=5 --可寫到connect by后面
          ??? connect by prior pid = id
          ?
          ??? ID?? NAME? PID? LEVEL
          ??? ---- ----- ---- ------
          ??? 5???? 121? 2??? 1
          ??? 2???? 11?? 1??? 2
          ??? 1???? 10?? 0??? 3

          ??? 說明:
          ??? 1、先從start with pid=1 句開始查詢 得到 2 11 1 1 =====> level置1;
          ??? 2、根據pid = id,查詢 id=1 句,得到 1 10 0 2 =====> level置2;
          ??? 3、根據pid = id,查詢 id=0 句,未查詢到后結束該樹枝;
          ??? 注:prior pid = id 句說明 pid是id的父節點,通過pid查詢id
          ?
          ?
          四、sys_connect_by_path函數講解:
          ?
          ??? sys_connect_by_path函數主要作用是可以把一個父節點下的所有子節點通過某個字符進行區分,在一個格中顯示。
          ??? 注意:一定是顯示子節點的,所以在connect by prior子句中注意區分好。
          ?
          ??? select level,sys_connect_by_path(id,'/') from TBL_TEST
          ??? start with pid=1
          ??? connect by prior pid = id;
          ?
          ??? select level,sys_connect_by_path(pid,'/') from TBL_TEST
          ??? start with id=5
          ??? connect by prior pid = id;
          ?
          ??? 可以比較這兩段代碼的運行結果與code example1的結果之間的差異,即可理解此函數用法
          ?
          ?
          ?
          ?
          關于sys_connect_by_path的具體應用,可參見以下轉載(這是一篇非常精彩的文章,作者是ITPUB的zhouwf0726版主)
          **************************************************************************************************
          ?
          start with ... connect by用法簡介 sql有向圖問題期待新解決方案
          ?
          /*******************************************************************************
          ?
          通過START WITH . . . CONNECT BY . . .子句來實現SQL的層次查詢.
          自從Oracle 9i開始,可以通過 SYS_CONNECT_BY_PATH 函數實現將父節點到當前行內容以“path”或者層次元素列表的形式顯示出來。
          ?
          自從Oracle 10g 中,還有其他更多關于層次查詢的新特性 。例如,有的時候用戶更關心的是每個層次分支中等級最低的內容。
          那么你就可以利用偽列函數CONNECT_BY_ISLEAF來判斷當前行是不是葉子。如果是葉子就會在偽列中顯示“1”,
          如果不是葉子而是一個分支(例如當前內容是其他行的父親)就顯示“0”。
          ?
          在Oracle 10g 之前的版本中,如果在你的樹中出現了環狀循環(如一個孩子節點引用一個父親節點),
          Oracle 就會報出一個錯誤提示:“ ORA-01436: CONNECT BY loop in user data”。如果不刪掉對父親的引用就無法執行查詢操作。
          而在 Oracle 10g 中,只要指定“NOCYCLE”就可以進行任意的查詢操作。與這個關鍵字相關的還有一個偽列——CONNECT_BY_ISCYCLE,
          如果在當前行中引用了某個父親節點的內容并在樹中出現了循環,那么該行的偽列中就會顯示“1”,否則就顯示“0”。
          ?
          The start with .. connect by clause can be used to select data that has a hierarchical relationship
          (usually some sort of parent->child, boss->employee or thing->parts).
          It is also being used when an sql execution plan is explained.
          ?
          syntax:
          select ... [start with initial-condition] connect by [nocycle] recurse-condition
          ?
          level
          With level it is possible to show the level in the hierarchical relation of all the data.
          ?
          --oracle 9i
          sys_connect_by_path
          With sys_connect_by_path it is possible to show the entire path from the top level down to the 'actual' child.
          ?
          --oracle 10g
          connect_by_root
          connect_by_root is a new operator that comes with Oracle 10g and enhances the ability to perform hierarchical queries.?
          connect_by_is_leaf
          connect_by_isleaf is a new operator that comes with Oracle 10g and enhances the ability to perform hierarchical queries.
          connect_by_iscycle
          connect_by_is_cycle is a new operator that comes with Oracle 10g and enhances the ability to perform hierarchical queries.
          ?
          --start with ... connect by ... 的處理機制
          How must a start with ... connect by select statement be read and interpreted?
          If Oracle encounters such an SQL statement, it proceeds as described in the following pseude code.
          ?
          for rec in (select * from some_table) loop
          ? if FULLFILLS_START_WITH_CONDITION(rec) then
          ??? RECURSE(rec, rec.child);
          ? end if;
          end loop;
          ?
          procedure RECURSE (rec in MATCHES_SELECT_STMT, new_parent IN field_type) is
          ? begin
          ? APPEND_RESULT_LIST(rec);????
          ? for rec_recurse in (select * from some_table) loop
          ??? if FULLFILLS_CONNECT_BY_CONDITION(rec_recurse.child, new_parent) then
          ????? RECURSE(rec_recurse,rec_recurse.child);
          ??? end if;
          ? end loop;
          end procedure RECURSE;
          ?
          created by zhouwf0726 2006.
          ?
          *******************************************************************************/
          ?
          --創建測試表,增加測試數據
          ?
          create table test(superid varchar2(20),id varchar2(20));
          ?
          insert into test values('0','1');
          insert into test values('0','2');
          ?
          insert into test values('1','11');
          insert into test values('1','12');
          ?
          insert into test values('2','21');
          insert into test values('2','22');
          ?
          insert into test values('11','111');
          insert into test values('11','112');
          ?
          insert into test values('12','121');
          insert into test values('12','122');
          ?
          insert into test values('21','211');
          insert into test values('21','212');
          ?
          insert into test values('22','221');
          insert into test values('22','222');
          ?
          commit;
          ?
          --層次查詢示例
          select level||'層',lpad(' ',level*5)||id id
          from test
          start with superid = '0' connect by prior id=superid;
          ?
          select level||'層',connect_by_isleaf,lpad(' ',level*5)||id id
          from test
          start with superid = '0' connect by prior id=superid;
          ?
          --給出兩個以前在"數據庫字符串分組相加之四"中的例子來理解start with ... connect by ...
          --功能:實現按照superid分組,把id用";"連接起來
          --實現:以下兩個例子都是通過構造2個偽列來實現connect by連接的。
          ?
          /*------method one------*/
          select superid,ltrim(max(sys_connect_by_path(id,';')),';') from(
          select superid,id,row_number() over(partition by superid order by superid) id1,
          row_number() over(order by superid) + dense_rank() over(order by superid) id2
          from test
          )
          start with id1=1 connect by prior id2 = id2 -1
          group by superid order by superid;
          ?
          /*------method two------*/
          select distinct superid,ltrim(first_value(id) over(partition by superid order by l desc),';')
          from(
          select superid,level l,sys_connect_by_path(id,';') id
          from(
          select superid,id,superid||rownum parent_rn,superid||to_char(rownum-1) rn
          from test
          )
          connect by prior parent_rn = rn
          );
          ?
          --下面的例子實現把一個整數的各個位上的數字相加,通過這個例子我們再次理解connect by.
          ?
          create or replace function f_digit_add(innum integer) return number
          is
          outnum integer;
          begin
          ??????? if innum<0 then
          ??????????????? return 0;
          ??????? end if;
          ??????? select sum(nm) into outnum from(
          ??????????????? select substr(innum,rownum,1) nm from dual connect by rownum<length(innum)
          ??????? );
          ??????? return outnum;
          end f_digit_add;
          /
          ?
          select f_digit_add(123456) from dual;
          ?

          /**********************************************************************************
          ***********************************************************************************
          下面是關于SQL解決有向圖問題,在這個例子中作者提到的錯誤
          select * from fares connect by prior arrive = depart start with depart = 'LHR';
          ERROR:
          ORA-01436: CONNECT BY loop in user data
          在oracle10g以上版本可以利用connect by的nocycle參數來解。有興趣的朋友研究用一條sql實現有向圖問題!
          ***********************************************************************************
          **********************************************************************************/
          ?
          一個常見的高級計算機科學問題可以在“有向圖”的范疇之下描述。有向圖是由一組向量和邊所連接的一組有限的節點。
          例如,一個節點可以想象為一座“城市”,而每個向量可以想象為兩座城市間的一個“航線”。
          有很多算法和論文講到如何解決每種可能路線的遍歷問題以及尋找最短路徑或者最小代價路徑的問題。
          這些算法中大部分都是過程化的,或者是使用遞歸方面來解決的。然而 SQL 的聲明性語言使得解決復雜的有向圖問題更加容易,
          而且不需要很多代碼。
          ?
          讓我們以兩座城市之間的航線為例子,創建一個表保存一些假想數據:
          ?
          create table airports
          (
          ??? code char(3) constraint airports_pk primary key,
          ??? description varchar2(200)
          );
          ?
          insert into airports values ('LHR','London Heathrow, UK');
          insert into airports values ('JFK','New York-Kennedy, USA');
          insert into airports values ('GRU','Sao Paulo, Brazil');
          ?
          create table fares
          (
          ??? depart char(3),
          ??? arrive char(3),
          ??? price number,
          ??? constraint fares_pk primary key (depart,arrive),
          ??? constraint fares_depart_fk foreign key (depart) references airports,
          ??? constraint fares_arrive_fk foreign key (arrive) references airports
          );
          ?
          insert into fares values('LHR','JFK',700);
          insert into fares values('JFK','GRU',600);
          insert into fares values('LHR','GRU',1500);
          insert into fares values('GRU','LHR',1600);
          ?
          不能使用CONNECT BY 語法來解決如何從倫敦到圣保羅,因為在圖中有數據產生一個環(從圣保羅飛回):
          ?
          select * from fares connect by prior arrive = depart start with depart = 'LHR';
          ERROR:
          ORA-01436: CONNECT BY loop in user data
          ?
          要解決有向圖問題,我們需要創建一個臨時表來保存兩個節點之間所有可能的路徑。我們必須注意不復制已經處理過的路徑,
          而且在這種情況下,我們不想路徑走回開始處的同一個地點。我還希望跟蹤到達目的地所需航程的數目,以及所走路線的描述。
          ?
          臨時表使用以下腳本創建:
          ?
          create global temporary table faretemp
          (
          ??? depart????? char(3),
          ??? arrive????? char(3),
          ??? hops??????? integer,
          ??? route?????? varchar2(30),
          ??? price?????? number,
          ??? constraint faretemp_pk primary key (depart,arrive)
          );
          ?
          一個簡單的視圖可以在稍微簡化這個例子中使用的代碼。視圖可以根據 fares 表中的單個航程計算從 faretemp 表中的一個路徑
          到達一下一個航程的數據:
          ?
          create or replace view nexthop
          as
          ??? select src.depart,
          ?????????? dst.arrive,
          ?????????? src.hops+1 hops,
          ?????????? src.route||','||dst.arrive route,
          ?????????? src.price + dst.price price
          ????? from faretemp src,fares dst
          ???? where src.arrive = dst.depart
          ?????? and dst.arrive != src.depart;
          /
          show errors;
          ?
          這個算法相當簡單。首先,使用 fares 表中的數據填充 faretemp 表,作為初始的航程。然后,取到我們剛才插入的所有數據,
          使用它們建立所有可能的二航程(two-hop)路徑。重復這一過程,直至在兩個節點之間創建了新路徑。
          循環過程將在節點間所有可能的路徑都被描述之后退出。如果我們只對某個開始條件感興趣,
          那么我們還可以限制第一次的插入從而減少裝載數據的量。下面是發現路徑的代碼:
          ?
          truncate table faretemp;
          begin
          ??? -- initial connections
          ??? insert into faretemp
          ???? select depart,arrive,1,depart||','||arrive,price from fares;
          ??? while sql%rowcount > 0 loop
          ??????? insert into faretemp
          ??????????? select depart,arrive,hops,route,price from nexthop
          ???????????? where (depart,arrive)
          ?????????????????? not in (select depart,arrive from faretemp);
          ??? end loop;
          end;
          /
          show errors;
          ?
          select * from faretemp order by depart,arrive;
          ?
          可以在表 A 中查看輸出。
          ?
          前面的數據有一個小問題。數據是點之間最短路徑(最小航程數)的集合。然而,從倫敦到圣保羅的航程卻不是最便宜的一個。
          ?
          要解決最便宜的費用問題,需要對我們的循環做一個改進,當在一個航程中發現一個更便宜的路線時使用這個路線代替原來的路線。
          修改后的代碼如下:
          ?
          truncate table faretemp;
          declare
          ??? l_count integer;
          begin
          ??? -- initial connections
          ??? insert into faretemp
          ??????? select depart,arrive,1,depart||','||arrive,price from fares;
          ??? l_count := sql%rowcount;
          ??? while l_count > 0 loop
          ??????? update faretemp
          ?????????? set (hops,route,price) =
          ????????????? (select hops,route,price from nexthop
          ??????????????? where depart = faretemp.depart
          ????????????????? and arrive = faretemp.arrive)
          ???????? where (depart,arrive) in
          ???????????? (select depart,arrive from nexthop
          ?????????????? where price < faretemp.price);
          ??????? l_count := sql%rowcount;
          ??????? insert into faretemp
          ??????????? select depart,arrive,hops,route,price from nexthop
          ???????????? where (depart,arrive)
          ?????????????? not in (select depart,arrive from faretemp);
          ??????? l_count := l_count + sql%rowcount;
          ??? end loop;
          end;
          /
          show errors;
          ?
          select * from faretemp order by depart,arrive;
          ?
          可能在表 B中查看輸出。
          ?
          算法發現LHR、JFK、GRU 路線比 LHR、GRU 路線便宜,所以用前者代替了后者。循環將在沒有更便宜的費用,并且沒有其它可能路線時退出。
          ?
          ------------------------------------------------------------------------------------------
          ?
          SYS_CONNECT_BY_PATH 函數
          自從Since Oracle 9i 開始,就可以通過 SYS_CONNECT_BY_PATH 函數實現將從父節點到當前行內容以“path”或者層次元素列表的形式顯示出來。 如下例所示:
          column path format a50
          select level,sys_connect_by_path(child,"/") path
          from hier
          start with parent is null
          connect by prior child = parent;
          ?
          LEVEL PATH
          -------- --------------------------------------------
          1 /Asia
          2 /Asia/China
          3 /Asia/China/Beijing
          2 /Asia/Japan
          3 /Asia/Japan/Osaka
          3 /Asia/Japan/Tokyo
          1 /Australia
          2 /Australia/New South Wales
          3 /Australia/New South Wales/Sydney
          1 /Europe
          2 /Europe/United Kingdom
          3 /Europe/United Kingdom/England
          4 /Europe/United Kingdom/England/London
          1 /North America
          2 /North America/Canada
          3 /North America/Canada/Ontario
          4 /North America/Canada/Ontario/Ottawa
          4 /North America/Canada/Ontario/Toronto
          2 /North America/USA
          3 /North America/USA/California
          4 /North America/USA/California/Redwood Shores
          ?
          CONNECT_BY_ISLEAF偽列

          在 Oracle 10g 中,還有其他更多關于層次查詢的新特性 。例如,有的時候用戶更關心的是每個層次分支中等級最低的內容。那么你就可以利用偽列函數CONNECT_BY_ISLEAF來判斷當前行是不是葉子。如果是葉子就會在偽列中顯示“1”,如果不是葉子而是一個分支(例如當前內容是其他行的父親)就顯示“0”。下給出了一個關于這個函數使用的例子:
          ?
          select connect_by_isleaf,sys_connect_by_path(child,"/") path
          from hier
          start with parent is null
          connect by prior child = parent;
          ?
          CONNECT_BY_ISLEAF PATH
          ---------------------------------- ------------
          0 /Asia
          0 /Asia/China
          1 /Asia/China/Beijing
          0 /Asia/Japan
          1 /Asia/Japan/Osaka
          1 /Asia/Japan/Tokyo
          0 /Australia
          0 /Australia/New South Wales
          1 /Australia/New South Wales/Sydney
          0 /Europe
          0 /Europe/United Kingdom
          0 /Europe/United Kingdom/England
          1 /Europe/United Kingdom/England/London
          0 /North America
          0 /North America/Canada
          0 /North America/Canada/Ontario
          1 /North America/Canada/Ontario/Ottawa
          1 /North America/Canada/Ontario/Toronto
          0 /North America/USA
          0 /North America/USA/California
          1 /North America/USA/California/Redwood Shores
          ?
          CONNECT_BY_ROOT偽列

          在Oracle 10g 中還有一個新操作——CONNECT_BY_ROOT。 它用在列名之前用于返回當前層的根節點。如下面的例子,我可以顯示出層次結構表中當前行數據所對應的最高等級節點的內容。
          ?
          select connect_by_root ,sys_connect_by_path(child,"/") path
          from hier
          start with parent is null
          connect by prior child = parent;
          ?
          CONNECT_BY_ROOT PATH
          ------------------------------ --------
          Asia /Asia
          Asia /Asia/China
          Asia /Asia/China/Beijing
          Asia /Asia/Japan
          Asia /Asia/Japan/Osaka
          Asia /Asia/Japan/Tokyo
          Australia /Australia
          Australia /Australia/New South Wales
          Australia /Australia/New South Wales/Sydney
          Europe /Europe
          Europe /Europe/United Kingdom
          Europe /Europe/United Kingdom/England
          Europe /Europe/United Kingdom/England/London
          North America /North America
          North America /North America/Canada
          North America /North America/Canada/Ontario
          North America /North America/Canada/Ontario/Ottawa
          North America /North America/Canada/Ontario/Toronto
          North America /North America/USA
          North America /North America/USA/California
          North America /North America/USA/California/Redwood Shores
          ?
          CONNECT_BY_ISCYCLE偽列

          在Oracle 10g 之前的版本中,如果在你的樹中出現了環狀循環(如一個孩子節點引用一個父親節點),Oracle 就會報出一個錯誤提示:“ ORA-01436: CONNECT BY loop in user data”。如果不刪掉對父親的引用就無法執行查詢操作。而在 Oracle 10g 中,只要指定“NOCYCLE”就可以進行任意的查詢操作。與這個關鍵字相關的還有一個偽列——CONNECT_BY_ISCYCLE, 如果在當前行中引用了某個父親節點的內容并在樹中出現了循環,那么該行的偽列中就會顯示“1”,否則就顯示“0”。如下例所示:
          ?
          create table hier2
          (
          parent number,
          child number
          );
          ?
          insert into hier2 values(null,1);
          insert into hier2 values(1,2);
          insert into hier2 values(2,3);
          insert into hier2 values(3,1);
          ?
          select connect_by_iscycle,sys_connect_by_path(child,"/") path
          from hier2
          start with parent is null
          connect by nocycle prior child = parent;
          ?
          CONNECT_BY_ISCYCLE PATH
          ------------------ -------
          0 /1
          0 /1/2
          1 /1/2/3?
          ?
          **************************************************************************************************
          ?
          ?
          ?
          ?
          posted on 2008-08-15 20:55 decode360 閱讀(1570) 評論(1)  編輯  收藏 所屬分類: 05.SQL

          評論

          # re: Connect By的簡單運用 2012-01-12 10:53 yigi
          你好呀,你在文章中提到
          select level from dual connect by level <=300
          上面這條語句在各個不同的Oracle版本、不同的client下面,顯示都是不同的。對于這個問題的解決,使用以下方法即可:
          select * from (select level from dual connect by level <=300);

          那你能告訴我,為什么這句話變成子句,就可以達到統一的結果呢,這就是經過了怎樣的算法過程?
            回復  更多評論
            

          主站蜘蛛池模板: 库伦旗| 定兴县| 舟山市| 海晏县| 大石桥市| 嘉义县| 漯河市| 屏东县| 东阿县| 马尔康县| 莆田市| 宜章县| 遵义县| 海兴县| 木里| 三原县| 清镇市| 肃南| 南皮县| 张北县| 白银市| 凯里市| 谢通门县| 长宁县| 郑州市| 定兴县| 盈江县| 浦城县| 岗巴县| 肇庆市| 南和县| 石城县| 台北县| 茌平县| 皋兰县| 鄂托克前旗| 美姑县| 临海市| 沙洋县| 瑞丽市| 仲巴县|