【问题标题】:Connect by query通过查询连接
【发布时间】:2016-08-06 04:56:04
【问题描述】:

我将分层数据存储在一个表中。当通过其分层路径(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)

【问题讨论】:

  • 如果你得到一个类似 countryName/stateName/townName 的路径怎么办?
  • 应该还是支持的。结果将包含最后三个条目(不包括 areaName)
  • 请执行并检查。它在您的查询中有效吗?输出是否符合预期?只是检查我们是否在同一页面上..我替换了 resource_name='townName',什么也没得到。
  • 同时删除这一行“WHEN 1 THEN 'areaName'”并将随后的 WHEN 值减 1。例如,WHEN 1 THEN 'townName' WHEN 2 THEN 'stateName' 等等...
  • 毫无疑问,我并不是说这会导致编写查询的困难——只是指出需要澄清规范。通常你会得到一个唯一的 id,但 OP 似乎想从实际名称开始。只要确保我们都在同一个页面上,并期望有时结果可能是多倍的。

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


【解决方案1】:

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


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

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

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

  3. Oracle 选择连续几代的子行。甲骨文优先 选择在步骤 2 中返回的行的子行,然后 那些孩子的孩子,等等。 Oracle 总是选择孩子 通过相对于电流评估 CONNECT BY 条件 父行。

  4. 如果查询包含没有连接的 WHERE 子句,则 Oracle 从层次结构中消除所有不满足 WHERE 子句的条件。 Oracle 评估此条件 单独的每一行,而不是删除一行的所有孩子 不满足条件。

为了优化这种情况,必须将查询更改如下(层次结构反转为更自然的自上而下顺序):

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 索引就足够了。这取决于存储在表中的数据的特性。

附:可以使用 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

【讨论】:

  • 解释得很好。我唯一的问题是 EnterpriseDB 不支持“CONNECT BY”子句中的多个条件。这就是我使用自下而上方法的原因。
  • 此限制是否也适用于使用 CTE (described here) 构建的递归查询?我用这种语法的例子更新了一个答案。
  • 在看到我们的原始回复后,我最终得到了一个类似的基于 CTE 的查询。是的,它在 EDB 中受支持,但语法略有不同。让我将其作为单独的答案发布。你太棒了!!感谢您的宝贵时间!
  • 好的,在“WITH”之后添加关键字“recursive”后,您的 CTE 查询在 EDB 中有效。所以查询应该以“WITH RECURSIVE hierarchy_query ...”开头。你能编辑你的查询吗?
  • 您是否也为此 CTE 查询推荐相同的索引?
【解决方案2】:
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 语句。只需提及资源名称即可获取父层次结构。

【讨论】:

  • 我应该提到 resource_name 条目不是唯一的。我们需要根据类似路径的 URL 来定位完整的层次结构。例如国家名称/州名称/城镇名称/区域名称。我们不需要任何“areaName”的层次结构,而是townName下的层次结构,它在stateName下,在countryName下
  • 我们会维护 parent_id 和 resource_id 之间的映射对吗?
  • 没错!!条目由 resource_id 和 parent_id 映射。
  • 正如我提到的,resource_name 不是唯一的,但组合是唯一的。这个查询不会给我预期的结果,但更多的结果在他们的叶子节点中具有相同的“resource_name”
  • 好的,让我再试一次。
【解决方案3】:

与@ThinkJet 提出的问题略有不同。这在 EDB 中有效并给出了预期的结果。

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 元素数。 此外,如果 URL 只有一个元素(如 /countryName),请从上述查询中删除 UNION 部分以获得预期结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-11
    • 2017-01-27
    • 2019-01-26
    • 1970-01-01
    • 2016-10-15
    • 2013-05-26
    • 2011-07-27
    • 1970-01-01
    相关资源
    最近更新 更多