在 SQL 中实现递归查询
Implementing a recursive query in SQL
我有一个关于使用递归 SQL 的问题,其中我有以下 table 结构
产品可以在多个组中(为了清楚起见,我没有使用 int )
CREATE TABLE ProductGroups(ProductName nvarchar(50), GroupName nvarchar(50))
INSERT INTO ProductGroups(ProductName, GroupName) values
('Product 1', 'Group 1'),
('Product 1', 'Group 2'),
('Product 2', 'Group 1'),
('Product 2', 'Group 6'),
('Product 3', 'Group 7'),
('Product 3', 'Group 8'),
('Product 4', 'Group 6')
+-----------+---------+
| Product | Group |
+-----------+---------+
| Product 1 | Group 1 |
| Product 1 | Group 2 |
| Product 2 | Group 1 |
| Product 2 | Group 6 |
| Product 3 | Group 7 |
| Product 3 | Group 8 |
| Product 4 | Group 6 |
+-----------+---------+
现在问题是我想找出所有相关产品
所以即如果我通过 Product 1 那么我需要以下结果
+-----------+---------+
| Product | Group |
+-----------+---------+
| Product 1 | Group 1 |
| Product 1 | Group 2 |
| Product 2 | Group 1 |
| Product 2 | Group 6 |
| Product 4 | Group 6 |
+-----------+---------+
所以基本上我想先找出产品 1 的所有组,然后我想找出每个组的所有产品,依此类推...
- 产品 1 => 第 1 组,第 2 组;
- Group 1 => Product 1, Product 2(Group 1 和 Product 1 已经存在所以应该避免,否则会进入无限
循环);
- 第 2 组 => 产品 1(已存在,同上);
- 产品 2 => 组 1、组 6(组 1 和产品 2 已存在)
- 组 6 => 产品 4
我认为递归 CTE 不可能做到这一点,因为每个递归定义只允许一个递归引用。
我确实设法用 while
循环实现了它,这可能比 cte 效率低:
declare @related table (ProductName nvarchar(50), GroupName nvarchar(50))
-- base case
insert @related select * from ProductGroups where ProductName='Product 1'
-- recursive step
while 1=1
begin
-- select * from @related -- uncomment to see progress
insert @related select p.*
from @related r
join ProductGroups p on p.GroupName=r.GroupName or p.ProductName=r.ProductName
left join @related rr on rr.ProductName=p.ProductName and rr.GroupName=p.GroupName
where rr.ProductName is null
if @@ROWCOUNT = 0
break;
end
select * from @related
您一定要小心上面的内容 - 在部署之前对实际大小的数据进行基准测试!
这并不容易。您正在建模 equivalence classes。 SQL 是一组语言,而您正在查看 class - 一组集合:
可以通过递归查询来完成,但这不是最佳选择,因为 SQL 服务器不允许您将递归 table 作为集合引用。所以你最终不得不保留一个路径字符串以避免无限循环。如果您使用整数,您可以用 hierarchyid
.
替换路径字符串
with r as (
select ProductName Root, ProductName, GroupName, convert(varchar(max), '/') Path from ProductGroups
union all
select r.Root, pg.ProductName, pg.GroupName, convert(varchar(max), r.Path + r.ProductName + ':' + r.GroupName + '/')
from r join ProductGroups pg on pg.GroupName=r.GroupName or pg.ProductName=r.ProductName
where r.Path not like '%' + pg.ProductName + ':' + pg.GroupName + '%'
)
select distinct ProductName, GroupName from r where Root='Product 1'
你可以做到这一点。
DECLARE @ProductGroups AS TABLE (
ProductName NVARCHAR(50) ,
GroupName NVARCHAR(50)
)
INSERT INTO @ProductGroups
( ProductName, GroupName )
VALUES ( 'Product 1', 'Group 1' ),
( 'Product 1', 'Group 2' ),
( 'Product 2', 'Group 1' ),
( 'Product 2', 'Group 6' ),
( 'Product 3', 'Group 7' ),
( 'Product 3', 'Group 8' ),
( 'Product 4', 'Group 6' );
;
WITH cte
AS ( SELECT a.ProductName
FROM @ProductGroups a
WHERE a.GroupName IN ( SELECT x.GroupName
FROM @ProductGroups x
WHERE x.ProductName = 'Product 1' )
),
cte2
AS ( SELECT GroupName
FROM @ProductGroups
WHERE ProductName IN ( SELECT x.ProductName
FROM cte x )
)
SELECT *
FROM @ProductGroups
WHERE GroupName IN ( SELECT x.GroupName
FROM cte2 x )
我有一个关于使用递归 SQL 的问题,其中我有以下 table 结构
产品可以在多个组中(为了清楚起见,我没有使用 int )
CREATE TABLE ProductGroups(ProductName nvarchar(50), GroupName nvarchar(50))
INSERT INTO ProductGroups(ProductName, GroupName) values
('Product 1', 'Group 1'),
('Product 1', 'Group 2'),
('Product 2', 'Group 1'),
('Product 2', 'Group 6'),
('Product 3', 'Group 7'),
('Product 3', 'Group 8'),
('Product 4', 'Group 6')
+-----------+---------+
| Product | Group |
+-----------+---------+
| Product 1 | Group 1 |
| Product 1 | Group 2 |
| Product 2 | Group 1 |
| Product 2 | Group 6 |
| Product 3 | Group 7 |
| Product 3 | Group 8 |
| Product 4 | Group 6 |
+-----------+---------+
现在问题是我想找出所有相关产品 所以即如果我通过 Product 1 那么我需要以下结果
+-----------+---------+
| Product | Group |
+-----------+---------+
| Product 1 | Group 1 |
| Product 1 | Group 2 |
| Product 2 | Group 1 |
| Product 2 | Group 6 |
| Product 4 | Group 6 |
+-----------+---------+
所以基本上我想先找出产品 1 的所有组,然后我想找出每个组的所有产品,依此类推...
- 产品 1 => 第 1 组,第 2 组;
- Group 1 => Product 1, Product 2(Group 1 和 Product 1 已经存在所以应该避免,否则会进入无限 循环);
- 第 2 组 => 产品 1(已存在,同上);
- 产品 2 => 组 1、组 6(组 1 和产品 2 已存在)
- 组 6 => 产品 4
我认为递归 CTE 不可能做到这一点,因为每个递归定义只允许一个递归引用。
我确实设法用 while
循环实现了它,这可能比 cte 效率低:
declare @related table (ProductName nvarchar(50), GroupName nvarchar(50))
-- base case
insert @related select * from ProductGroups where ProductName='Product 1'
-- recursive step
while 1=1
begin
-- select * from @related -- uncomment to see progress
insert @related select p.*
from @related r
join ProductGroups p on p.GroupName=r.GroupName or p.ProductName=r.ProductName
left join @related rr on rr.ProductName=p.ProductName and rr.GroupName=p.GroupName
where rr.ProductName is null
if @@ROWCOUNT = 0
break;
end
select * from @related
您一定要小心上面的内容 - 在部署之前对实际大小的数据进行基准测试!
这并不容易。您正在建模 equivalence classes。 SQL 是一组语言,而您正在查看 class - 一组集合:
可以通过递归查询来完成,但这不是最佳选择,因为 SQL 服务器不允许您将递归 table 作为集合引用。所以你最终不得不保留一个路径字符串以避免无限循环。如果您使用整数,您可以用 hierarchyid
.
with r as (
select ProductName Root, ProductName, GroupName, convert(varchar(max), '/') Path from ProductGroups
union all
select r.Root, pg.ProductName, pg.GroupName, convert(varchar(max), r.Path + r.ProductName + ':' + r.GroupName + '/')
from r join ProductGroups pg on pg.GroupName=r.GroupName or pg.ProductName=r.ProductName
where r.Path not like '%' + pg.ProductName + ':' + pg.GroupName + '%'
)
select distinct ProductName, GroupName from r where Root='Product 1'
你可以做到这一点。
DECLARE @ProductGroups AS TABLE (
ProductName NVARCHAR(50) ,
GroupName NVARCHAR(50)
)
INSERT INTO @ProductGroups
( ProductName, GroupName )
VALUES ( 'Product 1', 'Group 1' ),
( 'Product 1', 'Group 2' ),
( 'Product 2', 'Group 1' ),
( 'Product 2', 'Group 6' ),
( 'Product 3', 'Group 7' ),
( 'Product 3', 'Group 8' ),
( 'Product 4', 'Group 6' );
;
WITH cte
AS ( SELECT a.ProductName
FROM @ProductGroups a
WHERE a.GroupName IN ( SELECT x.GroupName
FROM @ProductGroups x
WHERE x.ProductName = 'Product 1' )
),
cte2
AS ( SELECT GroupName
FROM @ProductGroups
WHERE ProductName IN ( SELECT x.ProductName
FROM cte x )
)
SELECT *
FROM @ProductGroups
WHERE GroupName IN ( SELECT x.GroupName
FROM cte2 x )