从数据层次结构中获取指定级别类型的对象(Oracle 12 SQL)

时间:2016-03-06 16:20:28

标签: sql oracle oracle12c

object_type_t中的数据集如下所示:

OBJ_ID    PARENT_OBJ   OBJECT_TYPE   OBJECT_DESC
--------- ------------ ------------- -----------------------
ES01      <null>       ESTATE        Bucks Estate
BUI01     ES01         BUILDING      Leisure Centre
BUI02     ES01         BUILDING      Fire Station
BUI03     <null>       BUILDING      Housing Block
SQ01      BUI01        ROOM          Squash Court
BTR01     BUI02        ROOM          Bathroom 
AP01      BUI03        APARTMENT     Flat No. 1
AP02      BUI03        APARTMENT     Flat No. 2
BTR02     AP01         ROOM          Bathroom
BDR01     AP01         ROOM          Bedroom
BTR03     AP02         ROOM          Bathroom
SHR01     BTR01        OBJECT        Shower
SHR02     BTR02        OBJECT        Shower
SHR03     BTR03        OBJECT        Shower

在实际的等级术语中,看起来像这样:

ES01
  |--> BUI01
  |      |--> SQ01
  |--> BUI02
  |      |--> BTR01
                |--> SHR01
=======
BUI03
  |--> AP01
  |      |--> BTR02
  |      |      |--> SHR02
  |      |--> BDR01
  |--> AP02
         |--> BTR03
                |--> SHR03

我知道如何使用分层查询,例如CONNECT BY PRIOR。我也知道如何通过connect_by_root找到树的根。但我要做的是找到一个给定的&#34;等级&#34;一棵树(即不是根级别,而是给定对象的&#34; BUIDLING&#34;级别)。

例如,我希望能够查询层次结构中属于BUI01的每个对象。

然后相反,给定一个对象ID,我希望能够查询该对象的关联(例如)ROOM object_id

如果我可以将每个OBJECT_TYPE与给定的level相关联,那么事情就会容易得多。但是从上面的示例中可以看出,BUILDING并不总是出现在层次结构中的第1级。

我最初的想法是将数据提取为中间表格格式(可能是物化视图),如下所示。这将允许我通过物化视图上的简单SQL查询找到我想要的数据:

OBJ_ID    OBJECT_DESC      ESTATE_OBJ BUILDING_OBJ ROOM_OBJ
--------- ---------------- ---------- ------------ ----------
ES01      Bucks Estate     ES01
BUI01     Leisure Centre   ES01       BUI01
BUI02     Fire Station     ES01       BUI02
BUI03     Housing Block               BUI03
SQ01      Squash Court     ES01       BUI01        SQ01
BTR01     Bathroom         ES01       BUI02        BTR01
AP01      Flat No. 1                  BUI03
AP02      Flat No. 2                  BUI03
BTR02     Bathroom                    BUI03        BTR02
BDR01     Bedroom                     BUI03        BDR01
BTR03     Bathroom                    BUI03        BTR03
SHR01     Shower           ES01       BUI02        BTR01
SHR02     Shower                      BUI03        BTR02
SHR03     Shower                      BUI03        BTR03

但是(没有写PL / SLQ,我想避免),我还没有能够简明地构建一个能够实现这种表格格式的查询。

有谁知道我怎么做到这一点?可以吗?

解决方案必须在Oracle 12c中可执行。

另外:性能很重要,因为我的基础数据结构包含数十万行,结构可能非常深。所以更快的解决方案比慢速解决方案更受欢迎: - )

提前感谢您的帮助。

3 个答案:

答案 0 :(得分:2)

如果我正确理解您的需要,也许您可​​以避免表格视图,直接查询您的表格;

假设您要查找属于BUI01的所有对象,您可以尝试:

with test(OBJ_ID, PARENT_OBJ, OBJECT_TYPE, OBJECT_DESC) as
(
select 'ES01','','ESTATE','Bucks Estate' from dual union all
select 'BUI01','ES01','BUILDING','Leisure Centre' from dual union all
select 'BUI02','ES01','BUILDING','Fire Station' from dual union all
select 'BUI03','','BUILDING','Housing Block' from dual union all
select 'SQ01','BUI01','ROOM','Squash Court' from dual union all
select 'BTR01','BUI02','ROOM','Bathroom' from dual union all
select 'AP01','BUI03','APARTMENT','Flat No. 1' from dual union all
select 'AP02','BUI03','APARTMENT','Flat No. 2' from dual union all
select 'BTR02','AP01','ROOM','Bathroom' from dual union all
select 'BDR01','AP01','ROOM','Bedroom' from dual union all
select 'BTR03','AP02','ROOM','Bathroom' from dual union all
select 'SHR01','BTR01','OBJECT','Shower' from dual union all
select 'SHR02','BTR02','OBJECT','Shower' from dual union all
select 'SHR03','BTR03','OBJECT','Shower' from dual
)
select OBJECT_TYPE, OBJ_ID, OBJECT_DESC
from test
connect by prior obj_id = parent_obj
start with obj_ID = 'BUI01'

这考虑属于自己的BUI01;如果你不想这样,你可以用非常简单的方式修改查询来切断起始值。

相反,假设您正在寻找SHR01所在的房间,您可以尝试使用以下内容;它基本上是相同的递归思想,但按升序排列,而不是降序树:

with test(OBJ_ID, PARENT_OBJ, OBJECT_TYPE, OBJECT_DESC) as
(...
)
SELECT *
FROM (
        select OBJECT_TYPE, OBJ_ID, OBJECT_DESC
        from test
        connect by obj_id = PRIOR parent_obj
        start with obj_ID = 'SHR01'
)
WHERE object_type = 'ROOM'

在这两种情况下,您只扫描一次表,没有任何其他结构;这样,这就有机会足够快。

答案 1 :(得分:1)

所需的输出有3列,由对象类型决定。通常,这可以使用更多列进行扩展,每个列对应字段object_type的每个可能值。即使使用给定的示例数据,也可以设想另外一列apartment_obj

为了使这个通用而不需要像对象类型值那样多次自联接表,可以使用CONNECT BYPIVOT子句的组合:

SELECT  *
FROM    (
            SELECT     obj_id,
                       object_desc,
                       CONNECT_BY_ROOT obj_id      AS pivot_col_value,
                       CONNECT_BY_ROOT object_type AS pivot_col_name
            FROM       object_type_t
            -- skip the STARTS WITH clause to get all connected pairs
            CONNECT BY parent_obj = PRIOR obj_id
        )
PIVOT   (
            MAX(pivot_col_value) AS obj
            FOR (pivot_col_name) IN (
                'ESTATE'   AS estate,
                'BUILDING' AS building,
                'ROOM'     AS room
            )
        );

FOR ... IN子句有一个硬编码的所需列的名称列表 - 没有_obj后缀,因为它会在数据透视转换期间添加。

Oracle不允许动态检索此列表。注意:使用PIVOT XML syntax时,此规则有一个例外,但是您可以在一列中获取XML输出,然后您需要解析该列。那将是相当低效的。

带有CONNECT BY子句的子查询没有STARTS WITH子句,这使得该查询将任何记录作为起始点并从那里生成后代。与CONNECT_BY_ROOT选择一起,这允许生成所有连接的对的完整列表,其中层次结构中两者之间的距离可以是任何值。然后JOIN匹配两者的较深层,因此您获得该节点的所有祖先(包括节点本身)。然后这些祖先就会转入专栏。

CONNECT BY子查询也可以以向后遍历层次结构的方式编写。输出是相同的,但可能存在性能差异。如果是这样,我认为变化可以有更好的性能,但我没有在大型数据集上测试它:

SELECT  *
FROM    (
            SELECT     CONNECT_BY_ROOT obj_id      AS obj_id,
                       CONNECT_BY_ROOT object_desc AS object_desc,
                       obj_id                      AS pivot_col_value,
                       object_type                 AS pivot_col_name
            FROM       object_type_t
            -- Connect in backward direction:
            CONNECT BY obj_id = PRIOR parent_obj
        )
PIVOT   (
            MAX(pivot_col_value) AS obj
            FOR (pivot_col_name) IN (
                'ESTATE'   AS estate,
                'BUILDING' AS building,
                'ROOM'     AS room
            )
        );

请注意,在此变体中,CONNECT_BY_ROOT返回该对的更深节点,因为相反的遍历。

基于自联接的替代方案(上一个答案)

您可以使用此查询:

SELECT    t1.obj_id, 
          t1.object_desc,
          CASE 'ESTATE'
              WHEN t1.object_type THEN t1.obj_id
              WHEN t2.object_type THEN t2.obj_id
              WHEN t3.object_type THEN t3.obj_id
          END estate_obj,
          CASE 'BUILDING'
              WHEN t1.object_type THEN t1.obj_id
              WHEN t2.object_type THEN t2.obj_id
              WHEN t3.object_type THEN t3.obj_id
          END building_obj,
          CASE 'ROOM'
              WHEN t1.object_type THEN t1.obj_id
              WHEN t2.object_type THEN t2.obj_id
              WHEN t3.object_type THEN t3.obj_id
          END room_obj
FROM      object_type_t t1
LEFT JOIN object_type_t t2 ON t2.obj_id = t1.parent_obj
LEFT JOIN object_type_t t3 ON t3.obj_id = t2.parent_obj

答案 2 :(得分:0)

非常感谢@trincot的灵感,我已经制定了以下解决方案。它不是非常快速的生产数据,但它确实适用于任意深度的树。这不是动态的唯一方法是,必须提前选择要提取树的哪个级别,并且必须添加一个额外的列来捕获该数据。

原则是可以构建sys_connect_by_path列,并使用正则表达式从那里提取所需的级别数据。

WITH base_data (obj_id, parent_obj, object_type, object_desc) AS (
   SELECT 'ES01','','ESTATE','Bucks Estate' FROM dual union all
   SELECT 'BUI01','ES01','BUILDING','Leisure Centre' FROM dual union all
   SELECT 'BUI02','ES01','BUILDING','Fire Station' FROM dual union all
   SELECT 'BUI03','','BUILDING','Housing Block' FROM dual union all
   SELECT 'SQ01','BUI01','ROOM','Squash Court' FROM dual union all
   SELECT 'BTR01','BUI02','ROOM','Bathroom' FROM dual union all
   SELECT 'AP01','BUI03','APARTMENT','Flat No. 1' FROM dual union all
   SELECT 'AP02','BUI03','APARTMENT','Flat No. 2' FROM dual union all
   SELECT 'BTR02','AP01','ROOM','Bathroom' FROM dual union all
   SELECT 'BDR01','AP01','ROOM','Bedroom' FROM dual union all
   SELECT 'BTR03','AP02','ROOM','Bathroom' FROM dual union all
   SELECT 'SHR01','BTR01','OBJECT','Shower' FROM dual union all
   SELECT 'SHR02','BTR02','OBJECT','Shower' FROM dual union all
   SELECT 'SHR03','BTR03','OBJECT','Shower' FROM dual ),
obj_hierarchy AS (
   SELECT object_type, obj_id, object_desc, parent_obj, sys_connect_by_path(object_type||':'||obj_id,'/')||'/' r_path
   FROM   base_data
   START WITH parent_obj IS null
   CONNECT BY PRIOR obj_id = parent_obj
)
SELECT obj_id, object_desc,
       CASE
          WHEN instr(h.r_path, 'ESTATE:') > 1
          THEN regexp_replace (h.r_path,'.*/ESTATE:([^/]+).*$', '\1')
          ELSE ''
       END obj_estate,
       CASE
          WHEN instr(h.r_path, 'BUILDING:') > 1
          THEN regexp_replace (h.r_path,'.*/BUILDING:([^/]+).*$', '\1')
          ELSE ''
       END obj_building,
       CASE
          WHEN instr(h.r_path, 'ROOM:') > 1
          THEN regexp_replace (h.r_path,'.*/ROOM:([^/]+).*$', '\1')
          ELSE ''
       END obj_room
FROM   obj_hierarchy h