sql - 使用 T-SQL 连接来自所有父行的字符串的最有效方法是什么?

标签 sql sql-server database tsql stored-procedures

我有一个表,它有一个代表其父行的自引用外键。为了以最简单的形式说明问题,我们将使用此表:

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/

相关文章:

sql - 选择跨案例的不同计数

mysql - 填补 Mysql 中数据范围之间的空白

sql - 如何在列中找到第二个值

sql-server - 使用 SQL 将 XML 结构转置/展平为列

c# - 复杂的数据库操作

sql - ORA-00933: SQL 命令未在带有 join 的子查询中正确结束

sql - 防止 SQL Server 中的死锁

MySQL 错误 - #1062 - key 2 的重复条目 ' '

c# - Entity Framework : Where the heck is it getting these columns from?

ruby-on-rails - ' rake ' "in order to" 'rake'