自引用表上的 SQL 递归查询 (Oracle)
Posted
技术标签:
【中文标题】自引用表上的 SQL 递归查询 (Oracle)【英文标题】:SQL recursive query on self referencing table (Oracle) 【发布时间】:2011-01-20 03:05:56 【问题描述】:假设我有这个示例数据:
| Name | ID | PARENT_ID |
-----------------------------
| a1 | 1 | null |
| b2 | 2 | null |
| c3 | 3 | null |
| a1.d4 | 4 | 1 |
| a1.e5 | 5 | 1 |
| a1.d4.f6 | 6 | 4 |
| a1.d4.g7 | 7 | 4 |
| a1.e5.h8 | 8 | 5 |
| a2.i9 | 9 | 2 |
| a2.i9.j10| 10 | 9 |
我想选择所有从 accountId = 1 开始的记录,所以预期的结果是:
| Name | ID | PARENT_NAME | PARENT_ID |
-------------------------------------------
| a1 | 1 | null | null |
| a1.d4 | 4 | a1 | 1 |
| a1.e5 | 5 | a1 | 1 |
| a1.d4.f6 | 6 | a1.d4 | 4 |
| a1.d4.g7 | 7 | a1.d4 | 4 |
| a1.e5.h8 | 8 | a1.e5 | 5 |
我目前能够进行递归选择,但是我无法访问父引用中的数据,因此我无法返回 parent_name。我正在使用的代码是(适用于简单示例):
SELECT id, parent_id, name
FROM tbl
START WITH id = 1
CONNECT BY PRIOR id = parent_id
我应该使用什么 SQL 来进行上述检索?
未来搜索者的附加关键词:SQL 选择由同一张表中的父键表示的分层数据
【问题讨论】:
叫我固执,但我仍然不相信公认的答案是最好的。您能否发布建议的查询对您的数据的执行情况? @Samuel 我接受了 OMG 的答案,因为它很简单并且适合这种场景要求。我仍然不相信子查询的性能比加入更好:基于@OMG 关于 tkprof 的评论(我 \\assume\\ 他确实运行了测试)我认为可以安全地估计建议的解决方案是正确的。我目前没有足够的测试数据来产生有意义的结果( 【参考方案1】:你想这样做吗?
SELECT id, parent_id, name,
(select Name from tbl where id = t.parent_id) parent_name
FROM tbl t start with id = 1 CONNECT BY PRIOR id = parent_id
编辑 基于 OMG 的另一种选择(但我认为效果相同):
select
t1.id,
t1.parent_id,
t1.name,
t2.name AS parent_name,
t2.id AS parent_id
from
(select id, parent_id, name
from tbl
start with id = 1
connect by prior id = parent_id) t1
left join
tbl t2 on t2.id = t1.parent_id
【讨论】:
是的,但据我了解这是低效的,因为这会导致 RDBMS 对每个返回的结果进行额外的内部查询? @Maxim:你是对的——这是一个相关的子查询,每返回一行就会执行一次。虽然它有效,但它是可用的效率最低的方法。 @OMG 我不这么认为。优化器足够聪明,可以自行推断连接。看解释计划。 @Samuel:你能解释一下你在这种情况下使用内部选择的理由吗? @OMG 抱歉,我不能同意。标量子查询是外连接的有效替代方案,通常执行速度更快。看看这个oratechinfo.co.uk/scalar_subqueries.html【参考方案2】:用途:
SELECT t1.id,
t1.parent_id,
t1.name,
t2.name AS parent_name,
t2.id AS parent_id
FROM tbl t1
LEFT JOIN tbl t2 ON t2.id = t1.parent_id
START WITH t1.id = 1
CONNECT BY PRIOR t1.id = t1.parent_id
【讨论】:
我认为查询太早地加入了自己的不必要的工作。你不觉得吗? OP 列出了应该返回 NULL 的列,LEFT JOIN
会这样做 - 这也不是不必要的工作。
非常好,我很高兴。谢谢 :)。请注意,对于当前版本,您将收到“SQL 错误:ORA-00918:列不明确定义”,要解决此问题,需要将 SQL 调整为:START WITH t1.id = 1 CONNECT BY PRIOR t1.id = t1.parent_id
@Maxim:是的,我在 5 分钟后意识到我错过了表别名。已更正。
如果图中有一个圆圈会怎样? SELECT 会失败,还是会永远循环?【参考方案3】:
这有点麻烦,但我相信这应该可行(没有额外的连接)。这假设您可以选择一个永远不会出现在相关字段中的字符作为分隔符。
您可以在不嵌套选择的情况下执行此操作,但我发现它有四个对 SYS_CONNECT_BY_PATH 的引用更简洁。
select id,
parent_id,
case
when lvl <> 1
then substr(name_path,
instr(name_path,'|',1,lvl-1)+1,
instr(name_path,'|',1,lvl)
-instr(name_path,'|',1,lvl-1)-1)
end as name
from (
SELECT id, parent_id, sys_connect_by_path(name,'|') as name_path, level as lvl
FROM tbl
START WITH id = 1
CONNECT BY PRIOR id = parent_id)
【讨论】:
【参考方案4】:使用新的嵌套查询语法
with q(name, id, parent_id, parent_name) as (
select
t1.name, t1.id,
null as parent_id, null as parent_name
from t1
where t1.id = 1
union all
select
t1.name, t1.id,
q.id as parent_id, q.name as parent_name
from t1, q
where t1.parent_id = q.id
)
select * from q
【讨论】:
【参考方案5】:如何使用 PRIOR,
所以
SELECT id, parent_id, PRIOR name
FROM tbl
START WITH id = 1
CONNECT BY PRIOR id = parent_id`
或者如果你想获取根名称
SELECT id, parent_id, CONNECT_BY_ROOT name
FROM tbl
START WITH id = 1
CONNECT BY PRIOR id = parent_id
【讨论】:
很好的答案,比以前的答案更简洁。谢谢!以上是关于自引用表上的 SQL 递归查询 (Oracle)的主要内容,如果未能解决你的问题,请参考以下文章