我有一个表,它有一个代表其父行的自引用外键。为了以最简单的形式说明问题,我们将使用此表:
CREATE TABLE Folder(
id int IDENTITY(1,1) NOT NULL, --PK
parent_id int NULL, --FK
folder_name varchar(255) NOT NULL)
我想创建一个标量值函数,它将返回文件夹名称及其所有父文件夹名称的串联字符串,一直到根文件夹,根文件夹将由空 parent_id 值指定。
我目前的解决方案是一种我认为并不理想的程序方法。这是我正在做的:
CREATE FUNCTION dbo.GetEntireLineage
(@folderId INT)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE @lineage VARCHAR(MAX)
DECLARE @parentFolderId INT
SELECT @lineage = folder_name, @parentFolderId = parent_id FROM Folder WHERE id = @folderId
WHILE NOT @parentFolderId IS NULL
BEGIN
SET @parentFolderId = (SELECT parent_id FROM Folder WHERE parent_id = @parentFolderId)
SET @lineage = (SELECT @lineage + '-' + (SELECT folder_name FROM Folder WHERE parent_id = @parentFolderId))
END
RETURN @lineage
END
有没有更理想的方式来做到这一点?我是一名经验丰富的程序员,但 T-SQL 对我来说并不熟悉,而且我知道由于基于集合的数据的性质,这些问题通常需要不同的方法。非常感谢任何帮助寻找解决方案或任何其他处理 T-SQL 的提示和技巧。
最佳答案
要确定您需要测试的性能。我已经使用您的版本(稍作修改)和其他人建议的递归 CTE 版本进行了一些测试。
我在一个文件夹层次结构中使用了包含 2048 行的示例表,因此当将 2048 作为参数传递给函数时,完成了 2048 次连接。
循环版本:
create function GetEntireLineage1 (@id int)
returns varchar(max)
as
begin
declare @ret varchar(max)
select @ret = folder_name,
@id = parent_id
from Folder
where id = @id
while @@rowcount > 0
begin
select @ret = @ret + '-' + folder_name,
@id = parent_id
from Folder
where id = @id
end
return @ret
end
统计:
SQL Server Execution Times:
CPU time = 125 ms, elapsed time = 122 ms.
递归 CTE 版本:
create function GetEntireLineage2(@id int)
returns varchar(max)
begin
declare @ret varchar(max);
with cte(id, name) as
(
select f.parent_id,
cast(f.folder_name as varchar(max))
from Folder as f
where f.id = @id
union all
select f.parent_id,
c.name + '-' + f.folder_name
from Folder as f
inner join cte as c
on f.id = c.id
)
select @ret = name
from cte
where id is null
option (maxrecursion 0)
return @ret
end
统计:
SQL Server Execution Times:
CPU time = 187 ms, elapsed time = 183 ms.
所以在这两者之间,循环版本更有效,至少在我的测试数据上是这样。您需要对实际数据进行测试才能确定。
编辑
使用 for xml path('')
技巧的递归 CTE。
create function [dbo].[GetEntireLineage4](@id int)
returns varchar(max)
begin
declare @ret varchar(max) = '';
with cte(id, lvl, name) as
(
select f.parent_id,
1,
f.folder_name
from Folder as f
where f.id = @id
union all
select f.parent_id,
lvl + 1,
f.folder_name
from Folder as f
inner join cte as c
on f.id = c.id
)
select @ret = (select '-'+name
from cte
order by lvl
for xml path(''), type).value('.', 'varchar(max)')
option (maxrecursion 0)
return stuff(@ret, 1, 1, '')
end
统计:
SQL Server Execution Times:
CPU time = 31 ms, elapsed time = 37 ms.
关于sql - 使用 T-SQL 连接来自所有父行的字符串的最有效方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6688196/