Каков наиболее эффективный способ объединения строк из всех родительских строк с использованием T-SQL? - PullRequest
7 голосов
/ 14 июля 2011

У меня есть таблица с внешним ссылочным внешним ключом, который представляет ее родительскую строку.Чтобы проиллюстрировать проблему в ее простейшей форме, мы будем использовать эту таблицу:

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.

Ответы [ 4 ]

12 голосов
/ 14 июля 2011

Чтобы узнать наверняка о производительности, вам нужно протестировать.Я провел некоторое тестирование, используя вашу версию (слегка измененную) и рекурсивные версии 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.

Таким образом, между этими двумя версиями циклов более эффективна, по крайней мере, по моим тестовым данным.Вам необходимо проверить свои фактические данные, чтобы убедиться.

Редактировать

Рекурсивный CTE с трюком for xml path('').

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.
4 голосов
/ 14 июля 2011

Иерархия часто бывает излишней, если у вас нет действительно глубокой иерархии или очень больших наборов данных, которые могут воспользоваться преимуществами индексации. Это так быстро, как вы можете получить без изменения схемы.

 with recursiveCTE (parent_id,concatenated_name) as (
    select parent_id,folder_name
    from folder
    union all
    select f.parent_id,r.concatenated_name +f.folder_name
    from folder f
    inner join recursiveCTE r on r.parent_id = f.id
    )
    select folder_name from recursiveCTE 
4 голосов
/ 14 июля 2011

используйте рекурсивный запрос для обхода родителей, а затем этот метод для объединения в строку.

1 голос
/ 14 июля 2011

Это работает для вас:

 with cte (Parent_id, Path) as 
    (
    select Parent_Id,Folder_Name
    from folder
    union all
    select f.Parent_Id,r.Path + '\' + f.Folder_Name
    from Folder as f
    inner join cte as c on c.Parent_Id = f.Id
    )
    select Folder_Name from cte
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...