2012-05-08 10 views
14

Ho una tabella che contiene i dati di gerarchia - qualcosa come:SQL Server CTE -Trova genitore superiore perOid childID?

childID | parentID 
____________________ 
    1  |  5 
    5  |  9 
    9  |  20 
    2  |  4 
    3  |  7 
    7  |  8 
    8  |  8 
20  |  20 
    4  |  4 
    8  |  8 

output desiderato:

enter image description here

Ho creato un CTE ricorsiva, che mi trova in cima fatherID.

Qualcosa di simile:

;WITH cte AS (
       SELECT a.childID 
         ,a.parentID 
         ,1 AS lvl 
       FROM [Agent_Agents] a 
       WHERE a.childID = 214 //<==== value to begin with !! - thats part the problem 
       UNION ALL 
       SELECT tmp.childID 
         ,tmp.parentID 
         ,cte.lvl+1 
       FROM [Agent_Agents] tmp 
         INNER JOIN cte ON tmp.childID = cte.parentID 
       WHERE cte.childID<>cte.parentID 
      ) 
SELECT * 
FROM cte 
WHERE lvl = (
      SELECT MAX(lvl) 
      FROM cte 
     ) 

Il problema:

ho eseguito il CTE con esplicito valorechildID per cominciare (214)! Quindi mi dà solo il valore per 214. il CTE fa la parte ricorsiva e trova topParent per childID.

ma voglio ForEach row in the Table - per eseguire il CTE con il valore childID!

ho cercato di farlo con CROSS APPLY:

Qualcosa di simile:

select * from myTable Cross Apply (
            ;WITH cte AS (....) 
           ) 

ma IMHO (dal mio test !!) - è impossibile.

L'altra idea di mettere il CTE ricorsivo in un UDF ha una penalizzazione delle prestazioni (problema di udf come sappiamo).

Come posso creare questa query in modo che funzioni effettivamente? (o qualche soluzione vicina)?

qui è quello che ho provato

https://data.stackexchange.com/stackoverflow/query/edit/69458

risposta

15

Non sicuro di capire quello che state cercando, ma potrebbe essere questo.

;WITH c 
    AS (SELECT childid, 
       parentid, 
       parentid AS topParentID 
     FROM @myTable 
     WHERE childid = parentid 
     UNION ALL 
     SELECT T.childid, 
       T.parentid, 
       c.topparentid 
     FROM @myTable AS T 
       INNER JOIN c 
         ON T.parentid = c.childid 
     WHERE T.childid <> T.parentid) 
SELECT childid, 
     topparentid 
FROM c 
ORDER BY childid 

SE-Data

E 'lo stesso di answer dal marc_s con la differenza che io uso la variabile tavolo e il fatto che si dispone di childID = parentID per nodi principali in cui la risposta marc_s ha parent_ID = null per nodi principali. Secondo me è meglio avere parent_ID = null per i nodi root.

+0

Ive ha aggiunto una schermata di stampa per l'output desiderato. –

+0

@RoyiNamir - La mia query su SE-Data restituisce ciò che desideri. Ho appena aggiunto le colonne 'name' e' parentID'. –

+0

Sarò GLAD a sentire perché hai deciso di iniziare con il più alto - e non le foglie .... qual è la logica qui (anche se funziona)? perché non potremmo iniziare con le foglie verso le foglie più alte? –

19

Non puoi fare qualcosa di simile?

;WITH cte AS (....) 
SELECT 
    * 
FROM 
    cte 
CROSS APPLY 
    dbo.myTable tbl ON cte.XXX = tbl.XXX 

Mettere il CROSS APPLYdopo la definizione CTE - nella dichiarazione di uno SQL che fa riferimento alla CTE. Non funzionerebbe ??

O: - Girare attorno alla logica: eseguire un CTE "top-down", che preleva prima i nodi di livello superiore e quindi itera attraverso l'hiearchy. In questo modo, si può facilmente determinare il "top-level padre" nella prima parte del CTE ricorsiva - qualcosa di simile:

;WITH ChildParent AS 
(
    SELECT 
     ID, 
     ParentID = ISNULL(ParentID, -1), 
     SomeName, 
     PLevel = 1, -- defines level, 1 = TOP, 2 = immediate child nodes etc. 
     TopLevelFather = ID -- define "top-level" parent node 
    FROM dbo.[Agent_Agents] 
    WHERE ParentID IS NULL 

    UNION ALL 

    SELECT 
     a.ID, 
     ParentID = ISNULL(a.ParentID, -1), 
     a.SomeName, 
     PLevel = cp.PLevel + 1, 
     cp.TopLevelFather -- keep selecting the same value for all child nodes 
    FROM dbo.[Agent_Agents] a 
    INNER JOIN ChildParent cp ON r.ParentID = cp.ID 
) 
SELECT 
    ID, 
    ParentID, 
    SomeName, 
    PLevel, 
    TopLevelFather 
FROM ChildParent 

Questo darebbe nodi qualcosa di simile (in base ai dati di esempio, un po ' esteso):

ID ParentID SomeName  PLevel TopLevelFather 
20 -1  Top#20   1   20 
4 -1  TOP#4   1   4 
8 -1  TOP#8   1   8 
7  8  ChildID = 7  2   8 
3  7  ChildID = 3  3   8 
2  4  ChildID = 2  2   4 
9 20  ChildID = 9  2   20 
5  9  ChildID = 5  3   20 
1  5  ChildID = 1  4   20 

Ora, se si seleziona un particolare nodo figlio da questa uscita CTE, otterrete sempre tutte le informazioni che avete bisogno - tra cui il "livello" del bambino, e il suo nodo principale di livello superiore .

+0

Non penso che sarà wiork - dal CTE sa come eseguire e trovare uno (!!) padre superiore per ID. nota il valore 214 ..... come invio ID Foreach => Cte.DoWorkFor (ID)? –

+0

@RoyiNamir: con questo CTE 'ChildParent' - non puoi semplicemente fare' SELECT * FROM ChildParent WHERE ID = 214' e ottieni quello che ti serve ?? –

1

Non ho ancora il tempo di esaminare ulteriormente la tua domanda e non sono sicuro se ho capito o meno il tuo problema, ma non potresti usare questo svf per ottenere l'id del padre superiore?

CREATE FUNCTION [dbo].[getTopParent] (
    @ChildID INT 
) 

RETURNS int 
AS 
BEGIN 
    DECLARE @result int; 
    DECLARE @ParentID int; 

    SET @ParentID=(
     SELECT ParentID FROM ChildParent 
     WHERE ChildID = @ChildID 
    ) 

    IF(@ParentID IS NULL) 
     SET @result = @ChildID 
    ELSE 
     SET @result = [dbo].[getTopParent](@ParentID) 

    RETURN @result  
END 

allora si dovrebbe essere in grado di trovare ogni genitore top in questo modo:

SELECT ChildID 
    , [dbo].[getTopParent](ChildID) AS TopParentID 
FROM ChildParent 
+0

Grazie per aver risposto. tim. il problema (come sappiamo) che t udf riduce le prestazioni (mentre chiama in linea ...) ecco perché io tendo ad evitare questo tipo di soluzione. il problema (in generale): ho una tabella con 'id' e' parentId'. foreach line in (select * from ...) - ho bisogno di un'altra colonna che abbia il valore padre più alto. È stato provato con Cross Aply - non è possibile utilizzare il motore ricorsivo (CTE) con Cross Apply). –

+0

Ive ha aggiunto una schermata di stampa all'output desiderato. –

-1
select distinct 
     a.ChildID,a.ParentID, 
     --isnull(nullif(c.parentID,b.parentID),a.parentID) as toppa, 
     B.parentID 
     --,c.parentID 
     ,isnull(nullif(d.parentID,a.parentID),c.parentID) as toppa1,a.name 
from myTable a 
    inner join myTable c 
     on a.parentID=c.parentID 
    inner join myTable b 
     on b.childID=a.parentID 
    inner join myTable d 
     on d.childID=b.parentID 
+0

per favore spiegate la vostra risposta invece di darla semplicemente – ArtB

+0

Ho usato l'espressione CTE senza e quindi usando i join per ottenere il passaggio a padre padre per figlio e quindi le espressioni di tabella Common più importanti sono state introdotte in SQL Server 2005 non nel server 2000 quindi usando join per ottenere valori questo è il modo base per ottenere un valore parentid per un valore figlio – Bharani

0
select distinct 
     a.ChildID,a.ParentID, 
     --isnull(nullif(c.parentID,b.parentID),a.parentID) as toppa, 
     B.parentID 
     --,c.parentID 
     ,isnull(nullif(d.parentID,a.parentID),c.parentID) as toppa1,a.name 
from myTable a 
    inner join myTable c 
     on a.parentID=c.parentID 
    inner join myTable b 
     on b.childID=a.parentID 
    inner join myTable d 
     on d.childID=b.parentID 

ho utilizzando il senza espressione CTE e quindi utilizzando unisce per ottenere il passaggio alla fase genitore per il bambino e poi più importanti espressioni di tabella comune sono state introdotte in SQL Server 2005 non in Server 2000 in modo da utilizzare si unisce per ottenere valori di questo è il modo di base per ottenere parentid per un valore bambino

-1
With cte as 
(
Select ChileId,Name,ParentId from tblHerarchy 
where ParentId is null 
union ALL 
Select h.ChileId,h.Name,h.ParentId from cte 
inner join tblHerarchy h on h.ParentId=cte.ChileId 
) 
Select * from cte 
-1
With cteherarchy as 
(
Select ChileId,Name,ParentId from tblHerarchy 
where ParentId is null 
union ALL 
Select h.ChileId,h.Name,h.ParentId from cte 
inner join tblHerarchy h on h.ParentId=cte.ChileId 
) 
Select * from cteherarchy 
+0

Questa soluzione non risponde alla domanda. I dati originali non hanno 'NULL' in' ParentID'. La query suggerita non produce l'output desiderato. –

0

enter image description here

select dbo.[fn_getIMCatPath](8) 
select Cat_id,Cat_name,dbo.[fn_getIMCatPath](cat_id) from im_category_master 

Create FUNCTION [dbo].[fn_getIMCatPath] (@ID INT) 
returns NVARCHAR(1000) 
AS 
BEGIN 
    DECLARE @Return NVARCHAR(1000), 
      @parentID INT, 
      @iCount INT 

    SET @iCount = 0 

    SELECT @Return = Cat_name, 
     @parentID = parent_id 
    FROM im_category_master 
    WHERE [cat_id] = @ID 

    WHILE @parentID IS NOT NULL 
    BEGIN 
     SELECT @Return = cat_name + '>' + @Return, 
       @parentID = parent_id 
     FROM im_category_master 
     WHERE [cat_id] = @parentID 

     SET @iCount = @iCount + 1 
     IF @parentID = -1 
     BEGIN 
     SET @parentID = NULL 
     END 
     IF @iCount > 10 
      BEGIN 
       SET @parentID = NULL 
       SET @Return = '' 
      END 
    END 

    RETURN @Return 
END 
0

Considerare questi dati di esempio e il rispettivo SQL per accedere ai record figlio insieme al loro genitore principale.

Sample DATA

codice SQL:

;WITH c AS (
    SELECT Id, Name, ParentId as CategoryId, 
      Id as MainCategoryId, Name AS MainCategory 
    FROM pmsItemCategory 
    WHERE ParentId is null 

    UNION ALL 

    SELECT T.Id, T.Name, T.ParentId, MainCategoryId, MainCategory 
    FROM pmsItemCategory AS T 
      INNER JOIN c ON T.ParentId = c.Id 
    WHERE T.ParentId is not null 
    ) 

SELECT Id, Name, CategoryId, MainCategoryId, MainCategory 
FROM c 
order by Id 
Problemi correlati