如何遍历 table 中带有 id 和 parentId 的路径?
How to traverse a path in a table with id & parentId?
假设我有一个 table 比如:
id | parentId | name
1 NULL A
2 1 B
3 2 C
4 1 E
5 3 E
我正在尝试编写一个我可以调用的标量函数:
SELECT dbo.GetId('A/B/C/E')
如果我们使用上面的引用 table,将产生“5”。该函数将执行以下步骤:
- Find the ID of 'A' which is 1
- Find the ID of 'B' whose parent is 'A' (id:1) which would be id:2
- Find the ID of 'C' whose parent is 'B' (id:2) which would be id:3
- Find the ID of 'E' whose parent is 'C' (id:3) which would be id:5
我试图用一个 WHILE 循环来完成它,但是它变得非常复杂非常快......只是认为必须有一个简单的方法来做到这一点。
我想我是根据@SeanLange 的建议使用递归 CTE(在评论中):
CREATE FUNCTION GetID
(
@path VARCHAR(MAX)
)
/* TEST:
SELECT dbo.GetID('A/B/C/E')
*/
RETURNS INT
AS
BEGIN
DECLARE @ID INT;
WITH cte AS (
SELECT p.id ,
p.parentId ,
CAST(p.name AS VARCHAR(MAX)) AS name
FROM tblT p
WHERE parentId IS NULL
UNION ALL
SELECT p.id ,
p.parentId ,
CAST(pcte.name + '/' + p.name AS VARCHAR(MAX)) AS name
FROM dbo.tblT p
INNER JOIN cte pcte ON
pcte.id = p.parentId
)
SELECT @ID = id
FROM cte
WHERE name = @path
RETURN @ID
END
这是一个基于您的示例数据和我理解的要求的功能性 rcte 示例。
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something
create table #Something
(
id int
, parentId int
, name char(1)
)
insert #Something
select 1, NULL, 'A' union all
select 2, 1, 'B' union all
select 3, 2, 'C' union all
select 4, 1, 'E' union all
select 5, 3, 'E'
declare @Root char(1) = 'A';
with MyData as
(
select *
from #Something
where name = @Root
union all
select s.*
from #Something s
join MyData d on d.id = s.parentId
)
select *
from MyData
请注意,如果您更改变量的值,输出将会调整。我会将其设为内联 table 值函数。
CTE
版本未优化获取分层数据的方式。 (Refer MSDN Blog)
你应该像下面提到的那样做。它经过了 1000 万条记录的测试,比 CTE 版本快 300 倍:)
Declare @table table(Id int, ParentId int, Name varchar(10))
insert into @table values(1,NULL,'A')
insert into @table values(2,1,'B')
insert into @table values(3,2,'C')
insert into @table values(4,1,'E')
insert into @table values(5,3,'E')
DECLARE @Counter tinyint = 0;
IF OBJECT_ID('TEMPDB..#ITEM') IS NOT NULL
DROP TABLE #ITEM
CREATE TABLE #ITEM
(
ID int not null
,ParentID int
,Name VARCHAR(MAX)
,lvl int not null
,RootID int not null
)
INSERT INTO #ITEM
(ID,lvl,ParentID,Name,RootID)
SELECT Id
,0 AS LVL
,ParentId
,Name
,Id AS RootID
FROM
@table
WHERE
ISNULL(ParentId,-1) = -1
WHILE @@ROWCOUNT > 0
BEGIN
SET @Counter += 1
insert into #ITEM(ID,ParentId,Name,lvl,RootID)
SELECT ci.ID
,ci.ParentId
,ci.Name
,@Counter as cntr
,ch.RootID
FROM
@table AS ci
INNER JOIN
#ITEM AS pr
ON
CI.ParentId=PR.ID
LEFT OUTER JOIN
#ITEM AS ch
ON ch.ID=pr.ID
WHERE
ISNULL(ci.ParentId, -1) > 0
AND PR.lvl = @Counter - 1
END
select * from #ITEM
假设我有一个 table 比如:
id | parentId | name
1 NULL A
2 1 B
3 2 C
4 1 E
5 3 E
我正在尝试编写一个我可以调用的标量函数:
SELECT dbo.GetId('A/B/C/E')
如果我们使用上面的引用 table,将产生“5”。该函数将执行以下步骤:
- Find the ID of 'A' which is 1
- Find the ID of 'B' whose parent is 'A' (id:1) which would be id:2
- Find the ID of 'C' whose parent is 'B' (id:2) which would be id:3
- Find the ID of 'E' whose parent is 'C' (id:3) which would be id:5
我试图用一个 WHILE 循环来完成它,但是它变得非常复杂非常快......只是认为必须有一个简单的方法来做到这一点。
我想我是根据@SeanLange 的建议使用递归 CTE(在评论中):
CREATE FUNCTION GetID
(
@path VARCHAR(MAX)
)
/* TEST:
SELECT dbo.GetID('A/B/C/E')
*/
RETURNS INT
AS
BEGIN
DECLARE @ID INT;
WITH cte AS (
SELECT p.id ,
p.parentId ,
CAST(p.name AS VARCHAR(MAX)) AS name
FROM tblT p
WHERE parentId IS NULL
UNION ALL
SELECT p.id ,
p.parentId ,
CAST(pcte.name + '/' + p.name AS VARCHAR(MAX)) AS name
FROM dbo.tblT p
INNER JOIN cte pcte ON
pcte.id = p.parentId
)
SELECT @ID = id
FROM cte
WHERE name = @path
RETURN @ID
END
这是一个基于您的示例数据和我理解的要求的功能性 rcte 示例。
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something
create table #Something
(
id int
, parentId int
, name char(1)
)
insert #Something
select 1, NULL, 'A' union all
select 2, 1, 'B' union all
select 3, 2, 'C' union all
select 4, 1, 'E' union all
select 5, 3, 'E'
declare @Root char(1) = 'A';
with MyData as
(
select *
from #Something
where name = @Root
union all
select s.*
from #Something s
join MyData d on d.id = s.parentId
)
select *
from MyData
请注意,如果您更改变量的值,输出将会调整。我会将其设为内联 table 值函数。
CTE
版本未优化获取分层数据的方式。 (Refer MSDN Blog)
你应该像下面提到的那样做。它经过了 1000 万条记录的测试,比 CTE 版本快 300 倍:)
Declare @table table(Id int, ParentId int, Name varchar(10))
insert into @table values(1,NULL,'A')
insert into @table values(2,1,'B')
insert into @table values(3,2,'C')
insert into @table values(4,1,'E')
insert into @table values(5,3,'E')
DECLARE @Counter tinyint = 0;
IF OBJECT_ID('TEMPDB..#ITEM') IS NOT NULL
DROP TABLE #ITEM
CREATE TABLE #ITEM
(
ID int not null
,ParentID int
,Name VARCHAR(MAX)
,lvl int not null
,RootID int not null
)
INSERT INTO #ITEM
(ID,lvl,ParentID,Name,RootID)
SELECT Id
,0 AS LVL
,ParentId
,Name
,Id AS RootID
FROM
@table
WHERE
ISNULL(ParentId,-1) = -1
WHILE @@ROWCOUNT > 0
BEGIN
SET @Counter += 1
insert into #ITEM(ID,ParentId,Name,lvl,RootID)
SELECT ci.ID
,ci.ParentId
,ci.Name
,@Counter as cntr
,ch.RootID
FROM
@table AS ci
INNER JOIN
#ITEM AS pr
ON
CI.ParentId=PR.ID
LEFT OUTER JOIN
#ITEM AS ch
ON ch.ID=pr.ID
WHERE
ISNULL(ci.ParentId, -1) > 0
AND PR.lvl = @Counter - 1
END
select * from #ITEM