我将分层数据存储在表格中。当资源通过其分层路径(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)
答案 0 :(得分:3)
免责声明:我的主要经验属于Oracle DBMS,因此如果将解决方案应用于Postgres,请注意细节。
在已构建完整层次结构之后应用 Where
子句,因此在原始查询中,数据库引擎开始在任何级别检索具有指定resource_name
的数据,并为每个找到的记录构建完整树。过滤仅在下一步发生
Documentation:
Oracle选择层次结构的根行 - 那些行 满足START WITH条件。
Oracle选择每个根行的子行。每个子行必须 满足CONNECT BY条件相对于一个条件 根行。
Oracle选择连续几代子行。 Oracle首先 选择在步骤2中返回的行的子项,然后选择 这些孩子的孩子,等等。 Oracle总是选择孩子 通过评估关于电流的CONNECT BY条件 父行。
- 醇>
如果查询包含没有连接的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_id
和resource_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。每个级别的字符串都可以通过使用substr
和instr
函数从完整路径构建,如本示例中的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部分以获得预期的结果。