如何从查询结果中递归选择?

时间:2012-07-10 07:49:56

标签: sql sql-server tsql

如何以最有效的方式重用查询结果?

我有两个表:项目和关系。物品可以是孤独的物品,也可以是其他物品的孩子。关系在关系表中维护。项目由depNo,itemNo列唯一标识。以下是样本数据集:

create table items
(
itemId int identity (1,1) not null ,
depNo int not null,
itemNo int not null,
name varchar(50),
class int not null, -- 0 - unknown class, 1 - child item
constraint pk_depNo_itemNo primary key (depNo, itemNo)
);

create table relations
(
relId int identity (1,1) not null,
pDepNo int not null,
pItemNo int not null,
cDepNo int not null,
cItemNo int not null,
constraint pk_parent_child primary key (pDepNo, pItemNo, cDepNo, cItemNo)
);

insert into items values (1, 1, 'M1CItem1', 1);
insert into items values (1, 2, 'M1CItem2', 1);
insert into items values (1, 3, 'M1CItem3', 1);
insert into items values (2, 1, 'Master1', 0);
insert into items values (2, 2, 'LItem1', 0);
insert into items values (2, 3, 'LItem2', 0);
insert into items values (2, 4, 'LItem3', 0);
insert into items values (2, 5, 'Master2', 0);
insert into items values (2, 6, 'M2CItem1', 1);
insert into items values (2, 7, 'M2CItem1', 1);

insert into relations values (2, 1, 1, 1);
insert into relations values (2, 1, 1, 2);
insert into relations values (2, 1, 1, 3);
insert into relations values (2, 5, 2, 6);
insert into relations values (2, 5, 2, 7);    

如果item是子项,则以下查询选择满足查询条件的所有项目或其父项:

with qRes as (
select depNo, itemNo, name, class, pDepNo, pItemNo from items
left outer join relations
on depNo = cDepNo
and itemNo = cItemNo
where name like '%Item1'
)
-- select all results where item is not a child
select depNo, itemNo, name, class from qRes where class <> 1
union
-- select all parents of the children
select B.depNo, B.itemNo, B.name, B.class from qRes A
inner join items B
on A.pDepNo = B.depNo
and A.pItemNo = B.itemNo;

执行的查询将返回:

depNo   itemNo  name    class
2           1   Master1 0
2           2   LItem1  0
2           5   Master2 0

有没有更好的方法来解决这个问题?

2 个答案:

答案 0 :(得分:1)

如果你只有一个级别的递归,那么你的方法很好,如果你在层次结构中有很多级别,那么你可能想要考虑使用递归方法。

e.g。如果我改变你的一个关系:

UPDATE  Relations
SET     pItemNo = 6
WHERE   cItemNo = 7

然后,该行{DepNo: 2, ItemNo: 7, name: M2CItem1}成为{DepNo: 2, ItemNo: 6, name: M2CItem1}的孩子,而{DepNo: 2, ItemNo: 5, name: Master2}的孩子又是M2CItem1的孩子

以下内容将同时返回Master2;WITH CTE AS ( SELECT depNo, itemNo, name, class, pDepNo, pItemNo, 1 [RecursionLevel] FROM items LEFT JOIN relations ON DepNo = cDepNo AND ItemNo = cItemNo WHERE name like '%Item1' UNION ALL SELECT i.depNo, i.itemNo, i.name, i.class, r.pDepNo, r.pItemNo, RecursionLevel + 1 FROM CTE i INNER JOIN relations r ON i.pDepNo = r.cDepNo AND i.pItemNo = r.cItemNo ) SELECT DISTINCT c.DepNo, c.ItemNo, i.Name, i.Class FROM CTE c INNER JOIN Items i ON COALESCE(c.pDepNo, c.DepNo) = i.DepNo AND COALESCE(c.pItemNo, c.ItemNo) = i.ItemNo

;WITH CTE AS
(   SELECT  depNo, itemNo, name, class, pDepNo, pItemNo, 1 [RecursionLevel]
    FROM    items
            LEFT JOIN relations
                ON DepNo = cDepNo
                AND ItemNo = cItemNo
    WHERE   name like '%Item1'
    UNION ALL
    SELECT  i.depNo, i.itemNo, i.name, i.class, r.pDepNo, r.pItemNo, RecursionLevel + 1
    FROM    CTE i
            INNER JOIN relations r
                ON i.pDepNo = r.cDepNo
                AND i.pItemNo = r.cItemNo
), CTE2 AS
(   SELECT  c.DepNo, c.ItemNo, i.Name, i.Class, RecursionLevel, MAX(RecursionLevel) OVER(PARTITION BY c.DepNo, c.ItemNo) [MaxRecursionLevel]
    FROM    CTE c
            INNER JOIN Items i
                ON COALESCE(c.pDepNo, c.DepNo) = i.DepNo
                AND COALESCE(c.pItemNo, c.ItemNo) = i.ItemNo
)
SELECT  DepNo, ItemNo, Name, Class
FROM    CTE2
WHERE   Recursionlevel = maxRecursionLevel

但是,如果您只想返回最顶级的父级,则可以使用:

{DepNo: 2, ItemNo: 5, name: Master2}

这只会为行{DepNo: 2, ItemNo: 7, name: M2CItem1}返回{{1}},因为这是其父级的父级。

Working Examples on SQL Fiddle

顺便说一句,我认为您应该重新考虑您的架构?如果您正在使ItemNo和DepNo成为复合主键,那么您的标识列是什么?你应该选择其中一个,而不是两个。

答案 1 :(得分:0)

您可以使用UNION ALL语句在公用表表达式本身中执行子父级递归,但您的方法几乎就在那里。

如果您可以选择更改表,则可能需要查看Hierarchy数据类型。