2016-05-12 39 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的父是 'A' 的ID(ID:1),這將是找到父母是'B'(id:2)的'C'的ID,它將是id:3
  3. 找到父母爲'C'的'E'的ID(id:3 )這將是id:5

我試圖用一個WHILE循環來做,但它變得非常複雜非常快......只是想着必須有一個簡單的方法來做到這一點。

+0

您應儘量避免使用標量函數。他們效率非常低下。更糟糕的是,當你開始在它們內部投擲循環時。這聽起來像一個非常典型的遞歸類型。不知道爲什麼你會通過這個分隔的名稱列表。 –

+0

@SeanLange - 謝謝,我意識到標量函數的低效率 - 這只是我的要求。 – Denis

+0

奇怪的要求,但無論如何。如果你真的想要一些幫助,你可以發佈一些細節?這是一個遞歸查詢還是隻是試圖解析一個分隔列表併爲每個值做一個查詢? –

回答

0

我想我有它的基礎上@ 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 
0

這是根據您的樣本數據,並要求按我的理解功能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 

請注意,如果您更改變量的值,輸出將會調整。我會把這個內聯表值函數。

+0

3分鐘擊敗你:-)謝謝你的提示! – Denis

1

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