`

层次查询

阅读更多
层次查询

create table emp_hire as
select empno,mgr,ename from emp

select * from emp_hire
EMPNO	MGR	ENAME
5555	7900	ggg
7369	7902	SMITH
7499	7698	ALLEN
7521	7698	WARD
7566	7839	JONES
7654	7698	MA & RTIN
7698	7839	BLAKE
7782	7839	CLARK
7788	7566	SCOTT
7839	 	KING
7844	7698	TURNER
7900	7698	JAMES
7902	7566	FORD
7934	7782	MILLER
7876	7788	ADAMS

这些数据存在着层次关系
select 
   lpad('*',level,'*')||e.ename ename, 
   e.empno, 
   mgr fa_id, 
   (select ename from emp_hire where empno=e.mgr) fa_name
from emp_hire e
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR

ENAME	EMPNO	FA_ID	FA_NAME
*KING	7839	 	 
**JONES	7566	7839	KING
***SCOTT	7788	7566	JONES
****ADAMS	7876	7788	SCOTT
***FORD	7902	7566	JONES
****SMITH	7369	7902	FORD
**BLAKE	7698	7839	KING
***ALLEN	7499	7698	BLAKE
***WARD	7521	7698	BLAKE
***MA & RTIN	7654	7698	BLAKE
***TURNER	7844	7698	BLAKE
***JAMES	7900	7698	BLAKE
****ggg	5555	7900	JAMES
**CLARK	7782	7839	KING
***MILLER	7934	7782	CLARK



其中
start with -- this identifies all LEVEL=1 nodes in the tree

connect by -- describes how to walk from the parent nodes above to their children and their childrens children.

层次是一棵树, 又如同一个家族图谱
 

每一个子节电只有一个父节点;
每一个分支,最末端是叶节点;

SELECT 
ename  Employee, 
CONNECT_BY_ISLEAF  IsLeaf,
LEVEL lev, 
SYS_CONNECT_BY_PATH(ename, '/')  Path
FROM emp
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR

EMPLOYEE	ISLEAF	LEV	PATH
KING	0	1	/KING
JONES	0	2	/KING/JONES
SCOTT	0	3	/KING/JONES/SCOTT
ADAMS	1	4	/KING/JONES/SCOTT/ADAMS
FORD	0	3	/KING/JONES/FORD
SMITH	1	4	/KING/JONES/FORD/SMITH
BLAKE	0	2	/KING/BLAKE
ALLEN	1	3	/KING/BLAKE/ALLEN
WARD	1	3	/KING/BLAKE/WARD
MA & RTIN	1	3	/KING/BLAKE/MA & RTIN
TURNER	1	3	/KING/BLAKE/TURNER
JAMES	0	3	/KING/BLAKE/JAMES
ggg	1	4	/KING/BLAKE/JAMES/ggg
CLARK	0	2	/KING/CLARK
MILLER	1	3	/KING/CLARK/MILLER



检查一个层次是否存在闭循环
CONNECT_BY_ISCYCLE

找出根节点
CONNECT_BY_ROOT

SELECT 
ename  Employee, 
CONNECT_BY_ISCYCLE,
CONNECT_BY_ROOT ename 
FROM emp
    START WITH MGR IS NULL
    CONNECT BY NOCYCLE PRIOR EMPNO = MGR
EMPLOYEE	CONNECT_BY_ISCYCLE	CONNECT_BY_ROOTENAME
KING	0	KING
JONES	0	KING
SCOTT	0	KING
ADAMS	0	KING
FORD	0	KING
SMITH	0	KING
BLAKE	0	KING
ALLEN	0	KING
WARD	0	KING
MA & RTIN	0	KING
TURNER	0	KING
JAMES	0	KING
ggg	0	KING
CLARK	0	KING
MILLER	0	KING


层次查询的过滤条件
SELECT 
ename  Employee, 
CONNECT_BY_ISLEAF  IsLeaf,
LEVEL lev, 
SYS_CONNECT_BY_PATH(ename, '/')  Path
FROM emp
where level<3
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR

Restriction on LEVEL in WHERE Clauses In a [NOT] IN condition in a WHERE clause, if the right-hand side of the condition is a subquery, you cannot use LEVEL on the left-hand side of the condition. However, you can specify LEVEL in a subquery of the FROM clause to achieve the same result. For example, the following statement is not valid:

SELECT employee_id, last_name FROM employees
WHERE (employee_id, LEVEL)
IN (SELECT employee_id, 2 FROM employees)
START WITH employee_id = 2
CONNECT BY PRIOR employee_id = manager_id;
But the following statement is valid because it encapsulates the query containing the
LEVEL information in the FROM clause:

SELECT v.employee_id, v.last_name, v.lev
FROM
(SELECT employee_id, last_name, LEVEL lev
FROM employees v
START WITH employee_id = 100
CONNECT BY PRIOR employee_id = manager_id) v
WHERE (v.employee_id, v.lev) IN
(SELECT employee_id, 2 FROM employees);



SIBLINGS的排序

SELECT 
ename  Employee, 
CONNECT_BY_ISLEAF  IsLeaf,
LEVEL lev, 
SYS_CONNECT_BY_PATH(ename, '/')  Path
FROM emp
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR
ORDER SIBLINGS BY ename desc
EMPLOYEE	ISLEAF	LEV	PATH
KING	0	1	/KING
JONES	0	2	/KING/JONES
SCOTT	0	3	/KING/JONES/SCOTT
ADAMS	1	4	/KING/JONES/SCOTT/ADAMS
FORD	0	3	/KING/JONES/FORD
SMITH	1	4	/KING/JONES/FORD/SMITH
CLARK	0	2	/KING/CLARK
MILLER	1	3	/KING/CLARK/MILLER
BLAKE	0	2	/KING/BLAKE
WARD	1	3	/KING/BLAKE/WARD
TURNER	1	3	/KING/BLAKE/TURNER
MA & RTIN	1	3	/KING/BLAKE/MA & RTIN
JAMES	0	3	/KING/BLAKE/JAMES
ggg	1	4	/KING/BLAKE/JAMES/ggg
ALLEN	1	3	/KING/BLAKE/ALLEN

注意:ORDER SIBLINGS By一定与start with 与 connect by一起使用



检查树是否有闭循环
SELECT last_name "Employee", CONNECT_BY_ISCYCLE "Cycle",
LEVEL, SYS_CONNECT_BY_PATH(last_name, '/') "Path"
FROM employees
WHERE level <= 3 AND department_id = 80
START WITH last_name = 'King'
CONNECT BY NOCYCLE PRIOR employee_id = manager_id AND LEVEL <= 4;

Employee	Cycle	LEVEL	Path
King	0	1	/King
Russell	0	2	/King/Russell
Tucker	0	3	/King/Russell/Tucker
Bernstein	0	3	/King/Russell/Bernstein
Hall	0	3	/King/Russell/Hall
Olsen	0	3	/King/Russell/Olsen
Cambrault	0	3	/King/Russell/Cambrault
Tuvault	0	3	/King/Russell/Tuvault
Partners	0	2	/King/Partners
King	0	3	/King/Partners/King



层次查询的累计运算

将非叶节点的度量sal置为空
update emp_hire set sal=null
where empno in
(
select empno from
(
SELECT 
empno, 
CONNECT_BY_ISLEAF  IsLeaf,
LEVEL lev, 
SYS_CONNECT_BY_PATH(ename, '/')  Path
FROM emp
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR
)
where isleaf=0
)

察看数据

SELECT 
empno, 
CONNECT_BY_ISLEAF  IsLeaf,
LEVEL lev, 
SYS_CONNECT_BY_PATH(ename, '/')  Path,
sal
FROM emp_hire
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR

EMPNO	ISLEAF	LEV	PATH	SAL
7839	0	1	/KING	 
7566	0	2	/KING/JONES	 
7788	0	3	/KING/JONES/SCOTT	 
7876	1	4	/KING/JONES/SCOTT/ADAMS	1100.00
7902	0	3	/KING/JONES/FORD	 
7369	1	4	/KING/JONES/FORD/SMITH	800.00
7698	0	2	/KING/BLAKE	 
7499	1	3	/KING/BLAKE/ALLEN	1600.00
7521	1	3	/KING/BLAKE/WARD	1250.00
7654	1	3	/KING/BLAKE/MA & RTIN	1250.00
7844	1	3	/KING/BLAKE/TURNER	1500.00
7900	0	3	/KING/BLAKE/JAMES	 
5555	1	4	/KING/BLAKE/JAMES/ggg	0.00
7782	0	2	/KING/CLARK	 
7934	1	3	/KING/CLARK/MILLER	1300.00

select 
sum(sal),
 sum(power(0.5,(level-1))*sal) 
from emp_hire
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR

SUM(SAL)	SUM(POWER(0.5,(LEVEL-1))*SAL)
8800	1962.5


我们不能用以下的方法计算累计值
select 
SYS_CONNECT_BY_PATH(ename, '/')  Path, sum(sal),
 sum(power(0.5,(level-1))*sal) 
from emp_hire
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR
group by Path

我们可以找出一个节点的所有子孙节点,以及这些节点所在的level

select 
empno,   
SYS_CONNECT_BY_PATH(ename, '/')  Path, sal,
--,sum(power(0.5,(level-1))*sal) 
(
select sum(power(0.5,(level-1))*nvl(sal,0)) from emp_hire
 start with empno=e.empno
 connect by prior empno= mgr
) s_sal,
(
select sum(nvl(sal,0)) from emp_hire
 start with empno=e.empno
 connect by prior empno= mgr
) s1_sal
from emp_hire e
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR
EMPNO	PATH	SAL	S_SAL	S1_SAL
7839	/KING	 	1962.5	8800
7566	/KING/JONES	 	475	1900
7788	/KING/JONES/SCOTT	 	550	1100
7876	/KING/JONES/SCOTT/ADAMS	1100.00	1100	1100
7902	/KING/JONES/FORD	 	400	800
7369	/KING/JONES/FORD/SMITH	800.00	800	800
7698	/KING/BLAKE	 	2800	5600
7499	/KING/BLAKE/ALLEN	1600.00	1600	1600
7521	/KING/BLAKE/WARD	1250.00	1250	1250
7654	/KING/BLAKE/MA & RTIN	1250.00	1250	1250
7844	/KING/BLAKE/TURNER	1500.00	1500	1500
7900	/KING/BLAKE/JAMES	 	0	0
5555	/KING/BLAKE/JAMES/ggg	0.00	0	0
7782	/KING/CLARK	 	650	1300
7934	/KING/CLARK/MILLER	1300.00	1300	1300


也可以通过以下过程实现:

create type myTable is table of varchar2(100);

CREATE OR REPLACE FUNCTION str_to_table (
                 string_in    IN VARCHAR2 ,
                 delimiter_in IN VARCHAR2 DEFAULT ','
                 ) RETURN myTable IS
       v_wkg_str VARCHAR2 ( 32767 ) := string_in || delimiter_in;
       v_pos     PLS_INTEGER ;
       nt_return myTable := myTable();
       i_count  pls_integer;
    BEGIN
       LOOP
          v_pos := INSTR(v_wkg_str,delimiter_in);
          EXIT WHEN NVL(v_pos, 0 ) = 0  or v_wkg_str=delimiter_in;
          if TRIM (SUBSTR(v_wkg_str, 1 ,v_pos- 1 )) is not null  then
          nt_return. EXTEND ;
          nt_return(nt_return. LAST ) := TRIM (SUBSTR(v_wkg_str, 1 ,v_pos- 1 ));
          end if;
          v_wkg_str := SUBSTR(v_wkg_str,v_pos+ 1 );
       END LOOP ;
       RETURN nt_return;     
    END ;


declare 
--create or replace type v2 is table of varchar2(2000)
  c_path v2;
  c_level v1;
  c_empno v1;
  v_pos   integer;
  v_string  varchar2(2000);
begin
select 
empno,SYS_CONNECT_BY_PATH(empno, '/')  Path, level lev 
bulk collect into c_empno,c_path,c_level
from emp_hire
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR;
for i in 1..c_empno.count loop
v_string:='';
for j in 1..c_path.count loop

v_pos := INSTR(c_path(j),c_empno(i));
if v_pos>0 and c_empno(i)<>c_empno(j) then
v_string:=v_string||'/'||c_empno(j);

end if;
end loop; 
dbms_output.put_line(c_empno(i)||'   '||v_string);
end loop;      
end;

输出结果
7839   /7566/7788/7876/7902/7369/7698/7499/7521/7654/7844/7900/5555/7782/7934
7566   /7788/7876/7902/7369
7788   /7876
7876   
7902   /7369
7369   
7698   /7499/7521/7654/7844/7900/5555
7499   
7521   
7654   
7844   
7900   /5555
5555   
7782   /7934
7934

如果要计算累计结果则
declare 
--create or replace type v2 is table of varchar2(2000)
  c_path v2;
  c_level v1;
  c_empno v1;
  c_sal  v1;
  v_pos   integer;
  v_string  varchar2(2000);
  v_sal  number;
 
begin
select 
empno,SYS_CONNECT_BY_PATH(empno, '/')  Path, level lev, nvl(sal,0)
bulk collect into c_empno,c_path,c_level,c_sal
from emp_hire
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR;
for i in 1..c_empno.count loop
v_string:='';
v_sal:=0;
for j in 1..c_path.count loop

v_pos := INSTR(c_path(j),c_empno(i));
if v_pos>0   then
if c_empno(i)<>c_empno(j) then
v_string:=v_string||'/'||c_empno(j);
end if;
v_sal:=v_sal+c_sal(j);
end if;
end loop; 
dbms_output.put_line(c_empno(i)||'   '||v_string||'   '||v_sal);
end loop;      
end;

7839   8800   /7566/7788/7876/7902/7369/7698/7499/7521/7654/7844/7900/5555/7782/7934
7566   1900   /7788/7876/7902/7369
7788   1100   /7876
7876   1100   
7902   800   /7369
7369   800   
7698   5600   /7499/7521/7654/7844/7900/5555
7499   1600   
7521   1250   
7654   1250   
7844   1500   
7900   0   /5555
5555   0   
7782   1300   /7934
7934   1300


如果存在一定的累计规则
则
declare 
--create or replace type v2 is table of varchar2(2000)
  c_path v2;
  c_level v1;
  c_empno v1;
  c_sal  v1;
  v_pos   integer;
  v_string  varchar2(2000);
  v_sal  number;
 
begin
select 
empno,SYS_CONNECT_BY_PATH(empno, '/')  Path, level lev, nvl(sal,0)
bulk collect into c_empno,c_path,c_level,c_sal
from emp_hire
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR;
for i in 1..c_empno.count loop
v_string:='';
v_sal:=0;
for j in 1..c_path.count loop

v_pos := INSTR(c_path(j),c_empno(i));
if v_pos>0   then
if c_empno(i)<>c_empno(j) then
v_string:=v_string||'/'||c_empno(j);
end if;
v_sal:=v_sal+power(0.5,(c_level(j)-c_level(i)))*c_sal(j);
end if;
end loop; 
dbms_output.put_line(c_empno(i)||'   '||'    '||c_sal(i)||'    '||v_sal||'   '||v_string);
end loop;      
end;

7839       0    1962.5   /7566/7788/7876/7902/7369/7698/7499/7521/7654/7844/7900/5555/7782/7934
7566       0    475   /7788/7876/7902/7369
7788       0    550   /7876
7876       1100    1100   
7902       0    400   /7369
7369       800    800   
7698       0    2800   /7499/7521/7654/7844/7900/5555
7499       1600    1600   
7521       1250    1250   
7654       1250    1250   
7844       1500    1500   
7900       0    0   /5555
5555       0    0   
7782       0    650   /7934
7934       1300    1300  

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

SELECT 
empno,
ename  Employee, 
CONNECT_BY_ROOT ename,
level lev 
FROM (select * from emp_hire where ename<>'KING')
    START WITH MGR IS NULL
    CONNECT BY NOCYCLE PRIOR EMPNO = MGR


层次查询的效率

Explain plan for
select 
   lpad('*',level,'*')||e.ename ename, 
   e.empno, 
   mgr fa_id
from emp_hire e
    START WITH MGR IS NULL
    CONNECT BY PRIOR EMPNO = MGR
    
    
select * from table(dbms_xplan.display());


Plan hash value: 2148023842
 
----------------------------------------------------------------------------------------------------
| Id  | Operation                               | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |          |    15 |   495 |     3   (0)| 00:00:01 |
|*  1 |  CONNECT BY NO FILTERING WITH START-WITH|          |       |       |            |          |
|   2 |   TABLE ACCESS FULL                     | EMP_HIRE |    15 |   495 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("MGR"=PRIOR "EMPNO")
       filter("MGR" IS NULL)
 
Note
-----
   - dynamic sampling used for this statement

层次查询从本质上来说是递归运算,因此对于大的层次查询,可以分解为多个层次查询,从而减少计算量。

层次查询的副产品
产生自然数的查询
select rownum r  from dual  connect by level <= 100



层次向普通对称维度表的转换



 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics