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课(子查询)实验

Oracle开发者中级第4课(分析函数)实验

Oracle开发者中级第8课(Merge)实验

Oracle开发者中级第8课(Merge)实验

Oracle开发者中级第1课(Null)实验

Oracle开发者中级第6课(并集差集和交集)实验