【问题标题】:Fetch object of specified level-type from data hierarchy (Oracle 12 SQL)从数据层次结构中获取指定级别类型的对象(Oracle 12 SQL)
【发布时间】:2016-03-06 16:20:27
【问题描述】:

我在表 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 找到树的根。但我要做的是找到树的给定“级别”(即不是根级别,而是给定对象的“BUIDLING”级别)。

例如,我希望能够查询出层次结构中属于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 中可执行。

另外:性能很重要,因为我的底层数据结构包含几十万行,而且结构可能很深。因此,更快的解决方案将优于较慢的解决方案:-)

提前感谢您的帮助。

【问题讨论】:

    标签: sql oracle oracle12c


    【解决方案1】:

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

    假设你想找到所有属于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'
    

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

    【讨论】:

    • 这确实很好用,老实说,考虑到规范,这是最好的答案。但是,我的要求超出了原始调用中指定的要求,事实上,这确实需要我创建一个中间数据源。
    【解决方案2】:

    所需的输出有 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
    

    【讨论】:

    • 它确实有效,而且速度也很快——这很有吸引力。唯一的问题是,根据树的深度,您需要任意数量的 LEFT JOIN 语句,这反过来意味着它不适合一般情况。尽管如此,还是可以很好地使用 CASE 语句与文字组合来打开源列。
    • 我选择了这种方法,因为您的要求指定了文字列(最后三个)。如果它必须动态深度嵌套,那么您可能还需要显示更多列...
    • 显然,我解释得不够清楚,这是一个从真实数据中抽象出来的示例。我认为我的“另外”评论,[“结构可以很深”]会涵盖我。 :-S
    • 好的,我明白了。那么深度嵌套的数据将如何影响列数呢?您是否需要基于 object_type 动态添加列,以便您还可以使用给定数据自动添加列“APARTMENT_OBJ”?如果还有其他类型,它们是否也必须表示为列?
    • 很好的观察,很好的呈现!! :-/ 你说得对,我可能需要报告中任意数量的列,这不是实际要求。我不得不承认我没有考虑到那么详细的程度。
    【解决方案3】:

    非常感谢@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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-17
      • 1970-01-01
      • 1970-01-01
      • 2015-07-28
      • 2016-03-26
      • 1970-01-01
      • 2018-12-04
      • 1970-01-01
      相关资源
      最近更新 更多