按查询连接

时间:2016-04-14 14:39:56

标签: sql oracle connect-by hierarchical-query enterprisedb

我将分层数据存储在表格中。当资源通过其分层路径(grantParent / parent / resource)访问时,我需要使用CONNECT BY查询来定位资源。

注意:SQL命令是从EnterpriseDB导出的,但它也应该在Oracle中运行。

表格结构:

CREATE TABLE resource_hierarchy
(
  resource_id character varying(100) NOT NULL,
  resource_type integer NOT NULL,
  resource_name character varying(100),
  parent_id character varying(100)
)
WITH (
  OIDS=FALSE
);

数据:

INSERT INTO "resource_hierarchy" (resource_id,resource_type,resource_name,parent_id) VALUES ('36d27991', 3, 'areaName',    'a616f392');
INSERT INTO "resource_hierarchy" (resource_id,resource_type,resource_name,parent_id) VALUES ('a616f392', 3, 'townName',    'fcc1ebb7');
INSERT INTO "resource_hierarchy" (resource_id,resource_type,resource_name,parent_id) VALUES ('fcc1ebb7', 2, 'stateName',   '8369cc88');
INSERT INTO "resource_hierarchy" (resource_id,resource_type,resource_name,parent_id) VALUES ('8369cc88', 5, 'countryName', null);

现在,当我收到像

这样的路径时
countryName/stateName/townName/areaName

我正在执行像

这样的查询
select LEVEL,* from resource_hierarchy
WHERE resource_name = (
            CASE LEVEL 
                WHEN 1 THEN 'areaName'
                WHEN 2 THEN 'townName'
                WHEN 3 THEN 'stateName'
                WHEN 4 THEN 'countryName'
                ELSE ''
            END
         )
 connect by prior parent_id = resource_id
 start with resource_name = 'areaName';

我的预期结果是:

LEVEL   resource_id resource_type   resource_name   parent_id
-------------------------------------------------------------
1       36d27991    3               areaName        a616f392
2       a616f392    3               townName        fcc1ebb7
3       fcc1ebb7    2               stateName       8369cc88
4       8369cc88    5               countryName     <null>

此查询工作正常,但我不确定它是否会运行得更快,因为我的表格很像数十万个条目。

您可以针对我的要求优化此查询吗?

编辑:

解析上述查询:我已经定义了两个索引 - 一个在resource_id(主键),另一个在parent_id

Sort  (cost=66.85..66.86 rows=1 width=694)
  Sort Key: connectby_cte.siblingssortcol
  CTE prior
    ->  Recursive Union  (cost=0.00..65.83 rows=31 width=151)
      ->  WindowAgg  (cost=0.00..3.12 rows=1 width=83)
        ->  Seq Scan on resource_hierarchy  (cost=0.00..3.11 rows=1 width=83)
              Filter: ((resource_name)::text = 'areaName'::text)
      ->  WindowAgg  (cost=0.33..6.21 rows=3 width=151)
        ->  Hash Join  (cost=0.33..6.15 rows=3 width=151)
              Hash Cond: ((resource_hierarchy_1.resource_id)::text = (prior.parent_id)::text)
              Join Filter: connectby_cyclecheck(prior.recursionpath, (resource_hierarchy_1.parent_id)::text)
              ->  Seq Scan on resource_hierarchy resource_hierarchy_1  (cost=0.00..2.89 rows=89 width=83)
              ->  Hash  (cost=0.20..0.20 rows=10 width=286)
                ->  WorkTable Scan on prior  (cost=0.00..0.20 rows=10 width=286)
  ->  CTE Scan on prior connectby_cte  (cost=0.00..1.01 rows=1 width=694)
    Filter: ((resource_name)::text = CASE level WHEN 1 THEN 'areaName'::text WHEN 2 THEN 'townName'::text WHEN 3 THEN 'stateName'::text WHEN 4 THEN 'countryName'::text ELSE ''::text END)

3 个答案:

答案 0 :(得分:3)

免责声明:我的主要经验属于Oracle DBMS,因此如果将解决方案应用于Postgres,请注意细节。

在已构建完整层次结构之后应用

Where子句,因此在原始查询中,数据库引擎开始在任何级别检索具有指定resource_name的数据,并为每个找到的记录构建完整树。过滤仅在下一步发生 Documentation

  
      
  1. Oracle选择层次结构的根行 - 那些行   满足START WITH条件。

  2.   
  3. Oracle选择每个根行的子行。每个子行必须   满足CONNECT BY条件相对于一个条件   根行。

  4.   
  5. Oracle选择连续几代子行。 Oracle首先   选择在步骤2中返回的行的子项,然后选择   这些孩子的孩子,等等。 Oracle总是选择孩子   通过评估关于电流的CONNECT BY条件   父行。

  6.   
  7. 如果查询包含没有连接的WHERE子句,那么Oracle   从层次结构中删除不满足的所有行   WHERE子句的条件。 Oracle评估此条件   每行单独,而不是删除行中的所有子项   不符合条件的。

  8.   

要优化此情况,必须按如下方式更改查询(层次结构反转为更自然的自上而下顺序):

select 
  level, rh.* 
from 
  resource_hierarchy rh
start with 
  (resource_name = 'countryName')
  and 
  (parent_id is null) -- roots only
connect by 
  prior resource_id = parent_id
  and          
  -- at each step get only required records
  resource_name = (
    case level 
      when 1 then 'countryName'
      when 2 then 'stateName'
      when 3 then 'townName'
      when 4 then 'areaName'
      else null
    end
  )

可以根据CTE语法(Oracle recursive subquery factoring)编写相同的查询 以下是PostgreSQL CTE的变体,根据@Karthik_Murugan建议进行了更正:

with RECURSIVE hierarchy_query(lvl, resource_id) as (
    select
      1               lvl, 
      rh.resource_id  resource_id
    from
      resource_hierarchy rh
    where
     (resource_name = 'countryName') and (parent_id is null) 

  union all

    select
      hq.lvl+1        lvl,
      rh.resource_id  resource_id
    from
      hierarchy_query    hq,
      resource_hierarchy rh
    where
      rh.parent_id = hq.resource_id
      and
      -- at each step get only required records
      resource_name = (
        case (hq.lvl + 1)
          when 2 then 'stateName'
          when 3 then 'townName'
          when 4 then 'areaName'
          else null
        end
      )
)
select
  hq.lvl, rh.*
from
  hierarchy_query    hq,
  resource_hierarchy rh
where
  rh.resource_id = hq.resource_id
order by
  hq.lvl

这只是工作的一半,因为我们需要通过创建适当的索引来帮助数据库引擎定位记录 上面的查询包含两个搜索操作:
 1.找到要开始的记录;
 2.选择每个级别的记录。

对于第一个操作,我们需要将resource_name字段和可能的parent_id字段编入索引。
对于第二个操作字段,必须将parent_idresource_name编入索引。

create index X_RESOURCE_HIERARCHY_ROOT on RESOURCE_HIERARCHY (resource_name);
create index X_RESOURCE_HIERARCHY_TREE on RESOURCE_HIERARCHY (parent_id, resource_name);

也许仅创建X_RESOURCE_HIERARCHY_TREE索引就足够了。它取决于存储在表中的数据的特征。

P.S。每个级别的字符串都可以通过使用substrinstr函数从完整路径构建,如本示例中的Oracle所示:

with prm as (
  select 
    '/countryName/stateName/townName/areaName/' location_path 
  from dual
)
select 
  substr(location_path,
    instr(location_path,'/',1,level)+1,
    instr(location_path,'/',1,level+1)-instr(location_path,'/',1,level)-1
  )          
from prm connect by level < 7

答案 1 :(得分:1)

select 
     LEVEL, 
     resource_id, 
     resource_type, 
     resource_name, 
     parent_id 
from   
     resource_hierarchy 
connect by prior parent_id = resource_id 
start with UPPER(resource_name)= UPPER(:resource_name);

使用此方法,您不必使用CASE语句。只需提及资源名称即可获取父层次结构。

答案 2 :(得分:1)

与@ThinkJet提出的略有不同的查询。这适用于教育局并提供预期的结果。

WITH RECURSIVE rh (resource_id, resource_name, parent_id, level) AS 
(   
    SELECT resource_id, resource_name, parent_id, 1 as level FROM resource_hierarchy
    where resource_name = 'countryName' AND parent_id IS NULL
    UNION ALL
    SELECT cur.resource_id, cur.resource_name, cur.parent_id, level+1 FROM resource_hierarchy cur, rh prev WHERE cur.parent_id = prev.resource_id AND 
        cur.resource_name = (
                    CASE level 
                    WHEN 3 THEN 'areaName'
                    WHEN 2 THEN 'townName'
                    WHEN 1 THEN 'stateName'
                    END
                 )
)
SELECT * FROM rh

编辑:此查询甚至可以匹配部分匹配,但我们始终可以确保记录数=网址元素的数量。 此外,如果URL只有一个元素(如/ countryName),请从上面的查询中删除UNION部分以获得预期的结果。