Oracle开发者中级第7课(层级查询)实验
Posted dingdingfish
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle开发者中级第7课(层级查询)实验相关的知识,希望对你有一定的参考价值。
概述
本实验参考DevGym中的实验指南。
创建环境
创建表,依赖于HR示例Schema中的employees表:
create table employees as
select * from hr.employees
where department_id in ( 90, 100, 60 );
查看数据:
SQL> select * from employees;
EMPLOYEE_ID FIRST_NAME LAST_NAME EMAIL PHONE_NUMBER HIRE_DATE JOB_ID SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
______________ ______________ ____________ ___________ _______________ ____________ _____________ _________ _________________ _____________ ________________
103 Alexander Hunold AHUNOLD 590.423.4567 03-JAN-06 IT_PROG 9000 102 60
104 Bruce Ernst BERNST 590.423.4568 21-MAY-07 IT_PROG 6000 103 60
105 David Austin DAUSTIN 590.423.4569 25-JUN-05 IT_PROG 4800 103 60
106 Valli Pataballa VPATABAL 590.423.4560 05-FEB-06 IT_PROG 4800 103 60
107 Diana Lorentz DLORENTZ 590.423.5567 07-FEB-07 IT_PROG 4200 103 60
100 Steven King SKING 515.123.4567 17-JUN-03 AD_PRES 24000 90
101 Neena Kochhar NKOCHHAR 515.123.4568 21-SEP-05 AD_VP 17000 100 90
102 Lex De Haan LDEHAAN 515.123.4569 13-JAN-01 AD_VP 17000 100 90
108 Nancy Greenberg NGREENBE 515.124.4569 17-AUG-02 FI_MGR 12008 101 100
109 Daniel Faviet DFAVIET 515.124.4169 16-AUG-02 FI_ACCOUNT 9000 108 100
110 John Chen JCHEN 515.124.4269 28-SEP-05 FI_ACCOUNT 8200 108 100
111 Ismael Sciarra ISCIARRA 515.124.4369 30-SEP-05 FI_ACCOUNT 7700 108 100
112 Jose Manuel Urman JMURMAN 515.124.4469 07-MAR-06 FI_ACCOUNT 7800 108 100
113 Luis Popp LPOPP 515.124.4567 07-DEC-07 FI_ACCOUNT 6900 108 100
14 rows selected.
Connect By
Connect by是一种Oracle特有的使用SQL创建数据树的方法。它有两个关键子句,start with和connect by。
Start With指定树根,例如start with employee_id = 100
表示CEO,但更通用的方法是start with manager_id is null
,因为CEO上面没有manager了。
Connect By指定父子关系,这将链接存储父值和子值的列。您可以使用prior关键字从父行访问值。
例如:
select * from employees
start with manager_id is null
connect by prior employee_id = manager_id;
可以认为prior等同于parent,也就是父行的employee_id等于当前行的manager_id。
练习的答案如下,注意其将树做了颠倒:
select employee_id, first_name, last_name, manager_id
from employees
start with employee_id = 107
connect by prior manager_id = employee_id;
EMPLOYEE_ID FIRST_NAME LAST_NAME MANAGER_ID
----------- -------------------- ------------------------- ----------
107 Diana Lorentz 103
103 Alexander Hunold 102
102 Lex De Haan 100
100 Steven King
Recursive With
Oracle Database 11.2引入了另一种访问树的方法,即recursive with,这实际上是ANSI的语法。
Recursive With由Base Query和Recursive Query组成,前者定义根,相当于start with,后者相当于connect by。
例如在以下查询中:
with org_chart (
employee_id, first_name, last_name, manager_id
) as (
select employee_id, first_name, last_name, manager_id
from employees
where manager_id is null
union all
select e.employee_id, e.first_name, e.last_name, e.manager_id
from org_chart oc
join employees e
on e.manager_id = oc.employee_id
)
select * from org_chart;
EMPLOYEE_ID FIRST_NAME LAST_NAME MANAGER_ID
______________ ______________ ____________ _____________
100 Steven King
101 Neena Kochhar 100
102 Lex De Haan 100
108 Nancy Greenberg 101
103 Alexander Hunold 102
109 Daniel Faviet 108
110 John Chen 108
111 Ismael Sciarra 108
112 Jose Manuel Urman 108
113 Luis Popp 108
104 Bruce Ernst 103
105 David Austin 103
106 Valli Pataballa 103
107 Diana Lorentz 103
14 rows selected.
Base query如下,其定义了根节点,即CEO:
select employee_id, first_name, last_name, manager_id
from employees
where manager_id is null
Recursive Query如下,定义了父子关系:
select e.employee_id, e.first_name, e.last_name, e.manager_id
from org_chart oc
join employees e
on e.manager_id = oc.employee_id
其它就是固定格式了,模仿就好。
练习的答案为,通过这个例子,可以对照Connect By写法:
with org_chart (
employee_id, first_name, last_name, manager_id
) as (
select employee_id, first_name, last_name, manager_id
from employees
where employee_id = 107
union all
select e.employee_id, e.first_name, e.last_name, e.manager_id
from org_chart oc
join employees e
on e.employee_id = oc.manager_id
)
select * from org_chart;
EMPLOYEE_ID FIRST_NAME LAST_NAME MANAGER_ID
______________ _____________ ____________ _____________
107 Diana Lorentz 103
103 Alexander Hunold 102
102 Lex De Haan 100
100 Steven King
Level
如果能表示节点在书中的层级,就能知道哪些员工更资深,哪些在同一水平。
Connect By中的写法
首先来看Connect By方法。
level是一个伪列,表示树的深度,根为1,每下沉一级加1。
例如:
select level, employee_id, first_name, last_name, manager_id
from employees
start with manager_id is null
connect by prior employee_id = manager_id;
LEVEL EMPLOYEE_ID FIRST_NAME LAST_NAME MANAGER_ID
________ ______________ ______________ ____________ _____________
1 100 Steven King
2 101 Neena Kochhar 100
3 108 Nancy Greenberg 101
4 109 Daniel Faviet 108
4 110 John Chen 108
4 111 Ismael Sciarra 108
4 112 Jose Manuel Urman 108
4 113 Luis Popp 108
2 102 Lex De Haan 100
3 103 Alexander Hunold 102
4 104 Bruce Ernst 103
4 105 David Austin 103
4 106 Valli Pataballa 103
4 107 Diana Lorentz 103
14 rows selected.
再优化一下,可以用缩进表示层级,这样就更清晰了:
select level, employee_id,
lpad ( ' ', level, ' ' ) || first_name || ' ' || last_name name, manager_id
from employees
start with manager_id is null
connect by prior employee_id = manager_id;
LEVEL EMPLOYEE_ID NAME MANAGER_ID
________ ______________ ________________________ _____________
1 100 Steven King
2 101 Neena Kochhar 100
3 108 Nancy Greenberg 101
4 109 Daniel Faviet 108
4 110 John Chen 108
4 111 Ismael Sciarra 108
4 112 Jose Manuel Urman 108
4 113 Luis Popp 108
2 102 Lex De Haan 100
3 103 Alexander Hunold 102
4 104 Bruce Ernst 103
4 105 David Austin 103
4 106 Valli Pataballa 103
4 107 Diana Lorentz 103
14 rows selected.
Recursive With中的写法
由于没有level伪列,必须自己构建了:
with org_chart (
employee_id, first_name, last_name, manager_id, lvl
) as (
select employee_id, first_name, last_name, manager_id, 1 lvl
from employees
where manager_id is null
union all
select e.employee_id, e.first_name, e.last_name, e.manager_id, oc.lvl + 1
from org_chart oc
join employees e
on e.manager_id = oc.employee_id
)
select * from org_chart;
EMPLOYEE_ID FIRST_NAME LAST_NAME MANAGER_ID LVL
______________ ______________ ____________ _____________ ______
100 Steven King 1
101 Neena Kochhar 100 2
102 Lex De Haan 100 2
108 Nancy Greenberg 101 3
103 Alexander Hunold 102 3
109 Daniel Faviet 108 4
110 John Chen 108 4
111 Ismael Sciarra 108 4
112 Jose Manuel Urman 108 4
113 Luis Popp 108 4
104 Bruce Ernst 103 4
105 David Austin 103 4
106 Valli Pataballa 103 4
107 Diana Lorentz 103 4
14 rows selected.
Sorting Output: Connect By
构建分层查询时,数据库会按照与树结构匹配的顺序返回行。
Connect by 以深度优先搜索顺序返回行。 常规的order by无法使用。但是您可以保留深度优先树并对具有相同父级的行进行排序。 您可以使用 order by 的sibling子句执行此操作。
因此,要在他们之后显示经理的报告,按雇用日期(从头到尾)对同一经理的员工进行排序,您可以执行以下操作:
select level, employee_id, lpad ( ' ', level, ' ' ) || first_name || ' ' || last_name name, hire_date, manager_id
from employees
start with manager_id is null
connect by prior employee_id = manager_id
order siblings by hire_date;
LEVEL EMPLOYEE_ID NAME HIRE_DATE MANAGER_ID
________ ______________ ________________________ ____________ _____________
1 100 Steven King 17-JUN-03
2 102 Lex De Haan 13-JAN-01 100
3 103 Alexander Hunold 03-JAN-06 102
4 105 David Austin 25-JUN-05 103
4 106 Valli Pataballa 05-FEB-06 103
4 107 Diana Lorentz 07-FEB-07 103
4 104 Bruce Ernst 21-MAY-07 103
2 101 Neena Kochhar 21-SEP-05 100
3 108 Nancy Greenberg 17-AUG-02 101
4 109 Daniel Faviet 16-AUG-02 108
4 110 John Chen 28-SEP-05 108
4 111 Ismael Sciarra 30-SEP-05 108
4 112 Jose Manuel Urman 07-MAR-06 108
4 113 Luis Popp 07-DEC-07 108
14 rows selected.
Sorting Output: Recursive With
Recursive with可以定义遍历是深度优先还是广度优先。
Depth-First Search
深度优先的概念参见这里。下图来自维基百科:
with org_chart (
employee_id, first_name, last_name, hire_date, manager_id, lvl
) as (
select employee_id, first_name, last_name, hire_date, manager_id, 1 lvl
from employees
where manager_id is null
union all
select e.employee_id, e.first_name, e.last_name, e.hire_date, e.manager_id, oc.lvl + 1
from org_chart oc
join employees e
on e.manager_id = oc.employee_id
) search depth first by hire_date set hire_seq
select employee_id, lpad ( ' ', lvl, ' ' ) || first_name || ' ' || last_name name, hire_date, manager_id, lvl from org_chart
order by hire_seq;
EMPLOYEE_ID NAME HIRE_DATE MANAGER_ID LVL
______________ ________________________ ____________ _____________ ______
100 Steven King 17-JUN-03 1
102 Lex De Haan 13-JAN-01 100 2
103 Alexander Hunold 03-JAN-06 102 3
105 David Austin 25-JUN-05 103 4
106 Valli Pataballa 05-FEB-06 103 4
107 Diana Lorentz 07-FEB-07 103 4
104 Bruce Ernst 21-MAY-07 103 4
101 Neena Kochhar 21-SEP-05 100 2
108 Nancy Greenberg 17-AUG-02 101 3
109 Daniel Faviet 16-AUG-02 108 4
110 John Chen 28-SEP-05 108 4
111 Ismael Sciarra 30-SEP-05 108 4
112 Jose Manuel Urman 07-MAR-06 108 4
113 Luis Popp 07-DEC-07 108 4
14 rows selected.
Breadth-First Search
广度优先的概念见这里。下图来自维基百科:
只需将depth换成breadth,其它不变。结果很完美,缩进很给力:
with org_chart (
employee_id, first_name, last_name, hire_date, manager_id, lvl
) as (
select employee_id, first_name, last_name, hire_date, manager_id, 1 lvl
from employees
where manager_id is null
union all
select e.employee_id, e.first_name, e.last_name, e.hire_date, e.manager_id, oc.lvl + 1
from org_chart oc
join employees e
on e.manager_id = oc.employee_id
) search breadth first by hire_date set hire_seq
select employee_id, lpad ( ' ', lvl, ' ' ) || first_name || ' ' || last_name name, hire_date, manager_id, lvl from org_chart
order by hire_seq;
EMPLOYEE_ID NAME HIRE_DATE MANAGER_ID LVL
______________ ________________________ ____________ _____________ ______
100 Steven King 17-JUN-03 1
102 Lex De Haan 13-JAN-01 100 2
101 Neena Kochhar 21-SEP-05 100 2
108 Nancy Greenberg 17-AUG-02 101 3
103 Alexander Hunold 03-JAN-06 102 3
109 Daniel Faviet 16-AUG-02 108 4
105 David Austin 25-JUN-05 103 4
110 John Chen 28-SEP-05 108 4
111 Ismael Sciarra 30-SEP-05 108 4
106 Valli Pataballa 05-FEB-06 103 4
112 Jose Manuel Urman 07-MAR-06 108 4
107 Diana Lorentz 07-FEB-07 103 4
104 Bruce Ernst 21-MAY-07 103 4
113 Luis Popp 07-DEC-07 108 4
14 rows selected.
Detecting Loops
实际上,本文讨论的层级架构指的是类似于组织架构中这种单线管理的结构。如果是族谱,每个子节点会有两个父节点(父亲和母亲),这样会导致死循环。另外,数据的问题也可能导致死循环,因此有时需要避免这种问题。
例如,将CEO的经理设为107号员工:
update employees
set manager_id = 107
where employee_id = 100;
此时,系统会检测到循环并报错,因为同一节点被多次访问:
select * from employees
start with employee_id = 100
connect by prior employee_id = manager_id;
Error starting at line : 1 in command -
select * from employees
start with employee_id = 100
connect by prior employee_id = manager_id
Error report -
ORA-01436: CONNECT BY loop in user data
Connect By方法
此时可以使用nocycle子句,当重复访问同一节点时会不显示它,不会报错,然后继续遍历:
select * from employees
start with employee_id = 100
connect by nocycle prior employee_id = manager_id;
Recursive With方法
倒也简单,就是使用如下的语法,即检测employee_id是否有重复访问,如果有,则标为Y:
cycle employee_id set looped to 'Y' default 'N'
例如:
with org_chart (
employee_id, first_name, last_name, manager_id, department_id
) as (
select employee_id, first_name, last_name, manager_id, department_id
from employees
where employee_id = 100
union all
select e.employee_id, e.first_name, e.last_name, e.manager_id, e.department_id
from org_chart oc
join employees e
on e.manager_id = oc.employee_id
) cycle employee_id set looped to 'Y' default 'N'
select * from org_chart;
EMPLOYEE_ID FIRST_NAME LAST_NAME MANAGER_ID DEPARTMENT_ID LOOPED
______________ ______________ ____________ _____________ ________________ _________
100 Steven King 107 90 N
101 Neena Kochhar 100 90 N
102 Lex De Haan 100 90 N
108 Nancy Greenberg 101 100 N
103 Alexander Hunold 102 60 N
109 Daniel Faviet 108 100 N
110 John Chen 108 100 N
111 Ismael Sciarra 108 100 N
112 Jose Manuel Urman 108 100 N
113 Luis Popp 108 100 N
104 Bruce Ernst 103 60 N
105 David Austin 103 60 N
106 Valli Pataballa 103 60 N
107 Diana Lorentz 103 60 N
100 Steven King 107 90 Y
15 rows selected.
和Connect By不同,重复访问的行会显示在结果中,除非你过滤掉它。
另一个例子,是检测department_id是否有loop:
with org_chart (
employee_id, first_name, last_name, manager_id, department_id
) as (
select employee_id, first_name, last_name, manager_idOracle开发者中级第2课(子查询)实验