2016-05-12 12 views
0

Предположим, у меня есть таблица, как:Как пройти путь в таблице с id & parentId?

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'), который будет производить «5», если мы используем вышеупомянутую справочную таблицу. Функция будет выполнить следующие шаги:

  1. Найти идентификатор 'A', который является 1
  2. Найти идентификатор 'B', чей родитель 'А' (ID: 1), который будет id: 2
  3. Найти идентификатор 'C', родительский элемент которого является 'B' (id: 2), который будет идентификатором: 3
  4. Найти идентификатор 'E', родитель которого является 'C' (id: 3), который будет равен: 5

Я пытался сделать это с помощью цикла WHILE, но он очень быстро становился очень сложным ... Просто думая, что должен быть простой способ сделать это.

+0

Вы должны стараться избегать использования скалярной функции как можно больше. Они ужасно неэффективны. Еще хуже, когда вы начинаете бросать в них петли. Это звучит для меня как очень типичный рекурсивный cte. Не совсем уверен, но почему вы передадите этот список имен с разделителями. –

+0

@SeanLange - спасибо, я знаю о неэффективности скалярной функции - это просто требование, которое у меня есть. – Denis

+0

Странные требования, но что угодно. Если вам действительно нужна помощь, вы можете опубликовать некоторые подробности? Является ли это рекурсивным запросом или вы просто пытаетесь разобрать список с разделителями и выполнять запрос для каждого значения? –

ответ

0

Я думаю, что я его на основе @ SeanLange-х рекомендации использовать рекурсивное ОТВ (выше в комментариях):

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 
0

Ниже приведен пример функционального РКТО на основе данных выборочных и требований, как я понимаю их.

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 

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

+0

избил вас на 3 минуты :-) Спасибо за отзыв! – Denis

1

CTE версия не оптимизирована для получения иерархических данных. (Refer MSDN Blog)

Вы должны сделать что-то вроде упомянутого ниже. Он протестирован на 10 миллионов записей и в 300 раз быстрее, чем версия CTE :)

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