假设我们有一个由10个工作组成的表。有时工作可以参考其他工作。例如,我们说每项工作要么是完整的穿梭路线,要么是穿梭路线的一部分。如果它是一个完整的穿梭路线,那么[prev]和[next]字段都将为空。如果它只是路线的一条腿,那么这些字段中至少有一个将引用另一个作业,即路径序列中之前或之后的作业。
这是表格的样子。
我希望能够在表中查询1)[grab_me]为真的所有工作,或者2)它是任何一条腿(工作)[grab_me]的路线的一部分field设置为true。所以基本上,"抓住这些工作,以及他们所依附的任何东西。"
在这种情况下,所需的结果如下:
我已经能够通过两个递归CTE的联合来实现它,但我对SQL的理解并不是我想要的任何地方,我觉得它更简单方式。
这是我当前的设置:
declare @t table (job int, prev int, next int, grab_me bit)
insert @t (job, prev, next, grab_me)
values (1, null, null, 0),
(2, null, 3, 0),
(3, 2, 4, 0),
(4, 3, null, 0),
(5, null, null, 1),
(6, null, null, 1),
(7, null, 8, 0),
(8, 7, 9, 1),
(9, 8, 10, 0),
(10, 9, null, 0)
select * from @t;
with cte_1 as (
select job, prev, next, grab_me
from @t
where grab_me = 1
union all
select child.job, child.prev, child.next, child.grab_me
from @t child
join cte_1 parent
on parent.prev = child.job
),
cte_2 as (
select job, prev, next, grab_me
from @t
where grab_me = 1
union all
select child.job, child.prev, child.next, child.grab_me
from @t child
join cte_2 parent
on parent.next = child.job
)
select * from cte_1 union select * from cte_2;
答案 0 :(得分:4)
如果引入level
列,则可以使用单个cte执行此操作。父节点的级别为0,取决于您前往的方式为正数或负数。使用此伪列,您可以使递归查询起作用:
with cte_1 as (
select job, prev, next, grab_me, 0 as level
from @t
where grab_me = 1
union all
select child.job, child.prev, child.next, child.grab_me, parent.level + 1
from @t child
join cte_1 parent
on parent.prev = child.job and parent.level >= 0
union all
select child.job, child.prev, child.next, child.grab_me, parent.level - 1
from @t child
join cte_1 parent
on parent.next = child.job and parent.level <= 0
)
select * from cte_1 ;
result
job prev next grab_me level
----------- ----------- ----------- ------- -----------
5 NULL NULL 1 0
6 NULL NULL 1 0
8 7 9 1 0
7 NULL 8 0 1
9 8 10 0 -1
10 9 NULL 0 -2
(6 row(s) affected)
答案 1 :(得分:3)
这是一种技术上使用一个CTE的变体。我必须明确指出递归应该遍历哪个方向以避免无限循环。在这个问题之前我不知道在一个CTE中可能有multiple recursive members。
WITH
CTE
AS
(
select job, prev, next, grab_me, 1 AS UsePrev, 1 AS UseNext
from @t
where grab_me = 1
UNION ALL
select child.job, child.prev, child.next, child.grab_me, 1 AS UsePrev, 0 AS UseNext
from
@t child
inner join cte parent on
parent.prev = child.job
AND parent.UsePrev = 1
UNION ALL
select child.job, child.prev, child.next, child.grab_me, 0 AS UsePrev, 1 AS UseNext
from
@t child
inner join cte parent on
parent.next = child.job
AND parent.UseNext = 1
)
SELECT * FROM CTE;
结果集:
job prev next grab_me UsePrev UseNext
5 NULL NULL 1 1 1
6 NULL NULL 1 1 1
8 7 9 1 1 1
7 NULL 8 0 1 0
9 8 10 0 0 1
10 9 NULL 0 0 1
执行计划与第一个变体有很大不同。整个批处理的执行计划包括填充表变量,原始查询和新查询,INSERT为22%,原始查询为55%,新查询为23%。它太大了,不适合屏幕。
因此,新变体可能更有效,但必须使用真实数据进行检查。
单个CTE,第二个版本
事实证明,我们可以在没有两个递归成员的情况下做到这一点:
WITH
CTE
AS
(
select job, prev, next, grab_me, 1 AS UsePrev, 1 AS UseNext
from @t
where grab_me = 1
UNION ALL
select child.job, child.prev, child.next, child.grab_me
, CASE WHEN parent.prev = child.job AND parent.UsePrev = 1 THEN 1 ELSE 0 END AS UsePrev
, CASE WHEN parent.next = child.job AND parent.UseNext = 1 THEN 1 ELSE 0 END AS UseNext
from
@t AS child
inner join cte AS parent on
(parent.prev = child.job AND parent.UsePrev = 1)
OR
(parent.next = child.job AND parent.UseNext = 1)
)
SELECT * FROM CTE;
上一版本的执行计划再次简单,估计效率更高。
如果批处理包括初始化,原始查询,我的第一个变体和我的第二个变体,查询成本是:插入19%,原始48%,我的第一个版本20%,我的第二个版本13%。
插入强>
原始查询
第一个变体
第二个变体
在第二个版本中,我们可以移除额外的列UsePrev
,UseNext
并使用现有列prev
和next
来实现此目的。逻辑仍然是一样的。如果使用父级prev
链接从父级转到子级,则将此子级的next
链接(指向此父级)设置为NULL
,以便我们不会回溯再次到同一个父母。同样,如果我们使用父级next
链接从父级转到子级,则将此子级的prev
链接设置为NULL
。
WITH
CTE
AS
(
select job, prev, next, grab_me
from @t
where grab_me = 1
UNION ALL
select child.job
, CASE WHEN parent.next = child.job THEN NULL ELSE child.prev END AS prev
, CASE WHEN parent.prev = child.job THEN NULL ELSE child.next END AS next
, child.grab_me
from
@t AS child
inner join cte AS parent on
(parent.prev = child.job)
OR
(parent.next = child.job)
)
SELECT * FROM CTE;
执行计划与第二个变体非常相似,只有一个Compute Scalar
更少:
BTW,@cha在另一个答案中建议的查询执行计划与我的第一个变体的执行计划完全相同。