您的位置:首页 > 数据库 > Oracle

oracle 树形查找

2012-12-05 20:18 169 查看
connect by 是结构化查询中用到的,其基本语法是:

  select ... from tablename start with 条件1

  connect by 条件2

  where 条件3;

  例:

  select * from table

  start with org_id = 'HBHqfWGWPy'

  connect by prior org_id = parent_id;

  简单说来是将一个树状结构存储在一张表里,比如一个表中存在两个字段:

  org_id,parent_id那么通过表示每一条记录的parent是谁,就可以形成一个树状结构。

  用上述语法的查询可以取得这棵树的所有记录。

  其中:

  条件1 是根结点的限定语句,当然可以放宽限定条件,以取得多个根结点,实际就是多棵树。

  条件2 是连接条件,其中用PRIOR表示上一条记录,比如 CONNECT BY PRIOR org_id = parent_id就是说上一条记录的org_id 是本条记录的parent_id,即本记录的父亲是上一条记录。

  条件3 是过滤条件,用于对返回的所有记录进行过滤。

  简单介绍如下:

  早扫描树结构表时,需要依此访问树结构的每个节点,一个节点只能访问一次,其访问的步骤如下:

  第一步:从根节点开始;

  第二步:访问该节点;

  第三步:判断该节点有无未被访问的子节点,若有,则转向它最左侧的未被访问的子节,并执行第二步,否则执行第四步;

  第四步:若该节点为根节点,则访问完毕,否则执行第五步;

  第五步:返回到该节点的父节点,并执行第三步骤。

  总之:扫描整个树结构的过程也即是中序遍历树的过程。

  1. 树结构的描述

  树结构的数据存放在表中,数据之间的层次关系即父子关系,通过表中的列与列间的关系来描述, 如EMP表中的EMPNO和MGR。EMPNO表示该雇员的编号,MGR表示领导该雇员的人的编号,即子节点的MGR值等于父节点的EMPNO值。在表的 每一行中都有一个表示父节点的MGR(除根节点外),通过每个节点的父节点,就可以确定整个树结构。

  在SELECT命令中使用CONNECT BY 和蔼START WITH 子句可以查询表中的树型结构关系。其命令格式如下:

  SELECT 。。。

  CONNECT BY {PRIOR 列名1=列名2|列名1=PRIOR 裂名2}

  [START WITH];

  其中:CONNECT BY子句说明每行数据将是按层次顺序检索,并规定将表中的数据连入树型结构的关系中。PRIORY运算符必须放置在连接关系的两列中某一个的前面。对于节 点间的父子关系,PRIOR运算符在一侧表示父节点,在另一侧表示子节点,从而确定查找树结构是的顺序是自顶向下还是自底向上。在连接关系中,除了可以使
用列名外,还允许使用列表达式。START WITH 子句为可选项,用来标识哪个节点作为查找树型结构的根节点。若该子句被省略,则表示所有满足查询条件的行作为根节点。

  START WITH: 不但可以指定一个根节点,还可以指定多个根节点。

  2. 关于PRIOR

  运算符PRIOR被放置于等号前后的位置,决定着查询时的检索顺序。

  PRIOR被置于CONNECT BY子句中等号的前面时,则强制从根节点到叶节点的顺序检索,即由父节点向子节点方向通过树结构,我们称之为自顶向下的方式。如:

  CONNECT BY PRIOR EMPNO=MGR

  PIROR运算符被置于CONNECT BY 子句中等号的后面时,则强制从叶节点到根节点的顺序检索,即由子节点向父节点方向通过树结构,我们称之为自底向上的方式。例如:

  CONNECT BY EMPNO=PRIOR MGR

  在这种方式中也应指定一个开始的节点。

  3. 定义查找起始节点

  在自顶向下查询树结构时,不但可以从根节点开始,还可以定义任何节点为起始节点,以此开始向下查找。这样查找的结果就是以该节点为开始的结构树的一枝。

  4.使用LEVEL

  在具有树结构的表中,每一行数据都是树结构中的一个节点,由于节点所处的层次位置不同,所以每行记录都可以有一个层号。层号根据节点与根节点的距离确定。不论从哪个节点开始,该起始根节点的层号始终为1,根节点的子节点为2, 依此类推。图1.2就表示了树结构的层次。

  5.节点和分支的裁剪

  在对树结构进行查询时,可以去掉表中的某些行,也可以剪掉树中的一个分支,使用WHERE子句来限定树型结构中的单个节点,以去掉树中的单个节点,但它却不影响其后代节点(自顶向下检索时)或前辈节点(自底向顶检索时)。

  6.排序显示

  象在其它查询中一样,在树结构查询中也可以使用ORDER BY 子句,改变查询结果的显示顺序,而不必按照遍历树结构的顺序。

  ===================补充===================

  Start with...Connect By子句递归查询一般用于一个表维护树形结构的应用。

  创建示例表:

  CREATE TABLE TBL_TEST

  (

   ID NUMBER,

   NAME VARCHAR2(100 BYTE),

   PID NUMBER DEFAULT 0

  );

  插入测试数据:

  INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('1','10','0');

  INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('2','11','1');

  INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('3','20','0');

  INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('4','12','1');

  INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('5','121','2');

  从Root往树末梢递归

  select * from TBL_TEST

  start with id=1

  connect by prior id = pid

  从末梢往树ROOT递归

  select * from TBL_TEST

  start with id=5

  connect by prior pid = id

文章分类:数据库 Oracle是一种关系型数据库,在表中不可能以层次的关系存放数据,但是oracle提供了层次(树形)查询语句,使用树的遍历来获得层次关系的数据。

sql语法

select column,expr….

From table

Where conditions

Start with conditions

Connect by prior conditions

关键字和伪列介绍

Select

部分可以是字段或者表达式,或者伪列,如level,connect_by_isleaf等.

From

From后面可以是table,view但是只能是一个table,view中不能有多个表连接.

Where

条件限制了查询返回的行,但是其只影响节点自身,该节点的下层child不受影响

,属于节点的截断.

start with

确定遍历查询的开始点,可以是子查询,也可以不指定,不指定表示每个节点都作为起始节点来遍历一遍

connect by prior

确定遍历的方向,即是找子孙节点还是找祖先节点,遍历方向分为:自上向下,自下向上.

如果prior字段放在父字段前面,则表示要做自下向上的遍历;

如果prior字段放在子字段段前面,则表示要做自上向下的遍历;

和关键字prior放在=号左边右边没关系。

父字段:指明记录上一节点的字段如emp表中的mgr字段;

子字段:用来和父字段连接的字段,如emp标中的empno字段

level

查询的起始节点level为1,子孙依次增加,代表每个节点在家族树中的层次关系

connect_by_isleaf

是否叶子节点,如果查询时自顶向下,则叶子节点为1,如果自下向上,则根节点为1

connect _by_root column

查找子节点,叶子节点对应的根节点,10g新增的,这个很有用

prior

表示父节点,可以用在selelct部分,也可以用在connect by部分

nocycle

如果出现循环,在connect by中制定nocycle选项,查询将跳过循环部分的节点,避免10g之前的抱错。

connect_iscycle

如果出现循环,则为1,可以找出哪一条记录出现了循环。需要在connect by中加上nocycle选项

sys_connect_by_path

按path的顺序把字段连起来,做行列转换的时候需要。

Siblings

用于对树形遍历结果的排序,普通的排序会打乱树形遍历结果的层次关系,加上此关键字,可以不改变树形遍历结果的层次关系,只是在每一层内部按关键字排序,相当于做组内排序了,用法为order siblings by col

节点修剪和分支修剪

where子句的限制将会做节点的修剪,但是其后代不会受到影响,相对应,connect by中加上条件,将会把满足条件的节点以及后代修剪,属于分支修剪。

格式化输出

利用lpad函数和level,可以格式化输出记录间父子层次关系,方便阅读。

应用例子

利用lpad函数和level伪列,格式化输出

SQL> select empno,ename,lpad(empno,length(empno)+(level-1)*4,'-') from emp start with mgr is null connect by prior empno=mgr;

EMPNO ENAME LPAD(EMPNO,LENGTH(EMPNO)+(LEVE

----- ---------- --------------------------------------------------------------------------------

7839 KING 7839

7566 JONES ----7566

7788 SCOTT --------7788

7876 ADAMS ------------7876

7902 FORD --------7902

7369 SMITH ------------7369

7698 BLAKE ----7698

7499 ALLEN --------7499

7521 WARD --------7521

7654 MARTIN --------7654

7844 TURNER --------7844

7900 JAMES --------7900

7782 CLARK ----7782

7934 MILLER --------7934

自上向下,自下向上遍历查询

自下向上遍历

SQL> select empno,ename,level from emp start with empno=7788 connect by prior mgr=empno;

EMPNO ENAME LEVEL

----- ---------- ----------

7788 SCOTT 1

7566 JONES 2

7839 KING 3

自上向下遍历

SQL> select empno,ename,level from emp start with empno=7788 connect by prior empno=mgr;

EMPNO ENAME LEVEL

----- ---------- ----------

7788 SCOTT 1

7876 ADAMS 2

上面的sql语句等同于下面的sql

SQL> select empno,ename,level from emp start with empno=7788 connect by mgr=prior empno;

EMPNO ENAME LEVEL

----- ---------- ----------

7788 SCOTT 1

7876 ADAMS 2

节点修剪和分支修剪

比如对scott 7788做节点修剪,只影响scoot,adams不受影响

SQL> select empno,ename,lpad(empno,length(empno)+(level-1)*4,'-') from emp where empno<>7788 start with mgr is null connect by prior empno=mgr;

EMPNO ENAME LPAD(EMPNO,LENGTH(EMPNO)+(LEVE

----- ---------- --------------------------------------------------------------------------------

7839 KING 7839

7566 JONES ----7566

7876 ADAMS ------------7876

7902 FORD --------7902

7369 SMITH ------------7369

7698 BLAKE ----7698

7499 ALLEN --------7499

7521 WARD --------7521

7654 MARTIN --------7654

7844 TURNER --------7844

7900 JAMES --------7900

7782 CLARK ----7782

7934 MILLER --------7934

对比下面的分支修剪,可以看到2者的区别,下面的语句不但scott修剪掉了,而且其子孙节点adams也被修剪掉了

SQL> select empno,ename,lpad(empno,length(empno)+(level-1)*4,'-') from emp start with mgr is null connect by prior empno=mgr and empno<>7788;

EMPNO ENAME LPAD(EMPNO,LENGTH(EMPNO)+(LEVE

----- ---------- --------------------------------------------------------------------------------

7839 KING 7839

7566 JONES ----7566

7902 FORD --------7902

7369 SMITH ------------7369

7698 BLAKE ----7698

7499 ALLEN --------7499

7521 WARD --------7521

7654 MARTIN --------7654

7844 TURNER --------7844

7900 JAMES --------7900

7782 CLARK ----7782

7934 MILLER --------7934

Where条件和connect by中的条件也可以混合使用

查找父结点是7689且节点empno不是7521的所有纪录

SQL> select empno,ename,lpad(empno,length(empno)+(level-1)*4,'-') from emp wherepriorempno= 7698 start with mgr is null connect by prior empno=mgr and empno<>7521;

EMPNO ENAME LPAD(EMPNO,LENGTH(EMPNO)+(LEVE

----- ---------- --------------------------------------------------------------------------------

7499 ALLEN --------7499

7654 MARTIN --------7654

7844 TURNER --------7844

7900 JAMES --------7900

伪列Level,prior,connect_by_isleaf,connect_by_root的使用

SQL> select empno,ename,level,prior empno p_empno,connect_by_isleaf isleaf,connect_by_root empno r_empno from emp start with mgr is null connect by prior empno=mgr ;

EMPNO ENAME LEVEL P_EMPNO ISLEAF R_EMPNO

----- ---------- ---------- ---------- ---------- ----------

7839 KING 1 0 7839

7566 JONES 2 7839 0 7839

7788 SCOTT 3 7566 0 7839

7876 ADAMS 4 7788 1 7839

7902 FORD 3 7566 0 7839

7369 SMITH 4 7902 1 7839

7698 BLAKE 2 7839 0 7839

7499 ALLEN 3 7698 1 7839

7521 WARD 3 7698 1 7839

7654 MARTIN 3 7698 1 7839

7844 TURNER 3 7698 1 7839

7900 JAMES 3 7698 1 7839

7782 CLARK 2 7839 0 7839

7934 MILLER 3 7782 1 7839

伪列connect_by_iscycle, sys_connect_by_path的使用

SQL> select empno,ename,connect_by_iscycle iscycle,sys_connect_by_path(empno,'/') path_empno from emp start with mgr is null connect by nocycle prior empno=mgr ;

EMPNO ENAME ISCYCLE PATH_EMPNO

----- ---------- ---------- --------------------------------------------------------------------------------

7839 KING 0 /7839

7566 JONES 0 /7839/7566

7788 SCOTT 0 /7839/7566/7788

7876 ADAMS 0 /7839/7566/7788/7876

7902 FORD 0 /7839/7566/7902

7369 SMITH 0 /7839/7566/7902/7369

7698 BLAKE 0 /7839/7698

7499 ALLEN 0 /7839/7698/7499

7521 WARD 0 /7839/7698/7521

7654 MARTIN 0 /7839/7698/7654

7844 TURNER 0 /7839/7698/7844

7900 JAMES 0 /7839/7698/7900

7782 CLARK 0 /7839/7782

7934 MILLER 0 /7839/7782/7934

查看king下每一代的个数及平均sal

SQL> select level,count(*),trunc(avg(sal)) from emp start with mgr is null connect by prior empno=mgr group by level;

LEVEL COUNT(*) TRUNC(AVG(SAL))

---------- ---------- ---------------

1 1 5000

2 3 2758

4 2 950

3 8 1731

确认节点之间是否存在层级关系,如存在,列出层级关系

这可应用在产品的组装关系,员工的上下级领导关系的判断,如下面语句判断7839和7788是否有层级关系,如果返回结果,则表示存在层级关系

SQL> select empno,ename,level,sys_connect_by_path(empno,'/') from emp where empno=7788 start with empno=7839 connect by mgr=prior empno;

EMPNO ENAME LEVEL SYS_CONNECT_BY_PATH(EMPNO,'/')

----- ---------- ---------- --------------------------------------------------------------------------------

7788 SCOTT 3 /7839/7566/7788

查找每个部分的老大,增加了限制条件prior deptno<> deptno

SQL> select deptno,empno,ename,level from emp start with empno=7839 connect by mgr=prior empno and prior deptno<> deptno;

DEPTNO EMPNO ENAME LEVEL

------ ----- ---------- ----------

10 7839 KING 1

20 7566 JONES 2

30 7698 BLAKE 2

树形遍历排序order siblings by

先看一下用普通order取排序的情况

SQL> select deptno,empno,ename,lpad(empno,length(empno)+(level-1)*4,'-') from emp start with mgr is null connect by prior empno=mgr order by ename;

DEPTNO EMPNO ENAME LPAD(EMPNO,LENGTH(EMPNO)+(LEVE

------ ----- ---------- --------------------------------------------------------------------------------

20 7876 ADAMS ------------7876

30 7499 ALLEN --------7499

30 7698 BLAKE ----7698

10 7782 CLARK ----7782

20 7902 FORD --------7902

30 7900 JAMES --------7900

20 7566 JONES ----7566

10 7839 KING 7839

30 7654 MARTIN --------7654

10 7934 MILLER --------7934

20 7788 SCOTT --------7788

20 7369 SMITH ------------7369

30 7844 TURNER --------7844

30 7521 WARD --------7521

原先层次关系已经被打乱了,order关键词后面加上silbings

SQL> select deptno,empno,ename,lpad(empno,length(empno)+(level-1)*4,'-') from emp start with mgr is null connect by prior empno=mgr order siblings by ename;

DEPTNO EMPNO ENAME LPAD(EMPNO,LENGTH(EMPNO)+(LEVE

------ ----- ---------- --------------------------------------------------------------------------------

10 7839 KING 7839

30 7698 BLAKE ----7698

30 7499 ALLEN --------7499

30 7900 JAMES --------7900

30 7654 MARTIN --------7654

30 7844 TURNER --------7844

30 7521 WARD --------7521

10 7782 CLARK ----7782

10 7934 MILLER --------7934

20 7566 JONES ----7566

20 7902 FORD --------7902

20 7369 SMITH ------------7369

20 7788 SCOTT --------7788

20 7876 ADAMS ------------7876

层次关系没有变,每一层(level)内按ename排号序了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: