2012-05-08 8 views
14

Tengo una tabla que contiene los datos de jerarquía - algo así como:SQL Server CTE: ¿encuentra top parentID forEach childID?

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

salida deseada:

enter image description here

He creado un CTE recursiva, que me encuentra el fatherID superior.

Algo así como:

;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 
     ) 

El problema:

que ejecuta el CTE con explícita valorchildID, para empezar (214)! Me da el valor solo para 214. CTE realiza la parte recursiva y encuentra topParent para childID.

pero Quiero ForEach row in the Table - para ejecutar el CTE con el valor childID!

he tratado de hacerlo con CROSS APPLY:

Algo así como:

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

pero en mi humilde opinión (de mi prueba !!) - es imposible.

La otra idea de poner el CTE recursivo en un UDF tiene una penalización de rendimiento (el problema de udf como lo conocemos).

¿Cómo puedo crear esta consulta para que realmente funcione? (o alguna solución cercana)?

aquí es lo que he tratado

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

Respuesta

15

No estoy seguro de entender lo que está buscando, pero que podría ser esto.

;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

Es lo mismo que answer por marc_s con la diferencia de que utilizo la variable de mesa y el hecho de que tiene childID = parentID de nodos raíz, donde la respuesta por marc_s tiene parent_ID = null de nodos raíz. En mi opinión, es mejor tener parent_ID = null para nodos raíz.

+0

ive agregó una pantalla de impresión para la salida deseada. –

+0

@RoyiNamir - Mi consulta sobre SE-Data devuelve lo que desea. Acabo de agregar las columnas 'name' y' parentID'. –

+0

Me alegra saber por qué decidiste comenzar por el más alto, y no por las hojas ... ¿cuál es la lógica aquí (aunque funciona)? ¿Por qué no podríamos comenzar con las hojas hacia las hojas superiores? –

19

¿No puede hacer algo como esto?

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

Ponga la CROSS APPLYdespués la definición de CTE - en la declaración SQL que se hace referencia de nuevo a la CTE. ¿No funcionaría eso?

O: - Dale la vuelta a tu lógica: haz un CTE "de arriba hacia abajo" que primero selecciona los nodos de nivel superior y luego itera por la jerarquía. De esta manera, se puede determinar fácilmente el "padre de nivel superior" en la primera parte de la CTE recursiva - algo como esto:

;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 

Esto daría usted nodos algo como esto (basado en datos de la muestra, ligeramente extendido):

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 

Ahora bien, si se selecciona un nodo hijo en particular desde esta salida de CTE, que siempre obtendrá todas las informaciones que necesita - incluyendo el "nivel" del niño, y su nodo padre de alto nivel .

+0

yo no creo que vaya a wiork - CTE ya sabe cómo ejecutar y encontrar uno (!!) para la identificación del padre superior. observe el valor 214 ... ¿cómo enviaré Foreach ID => Cte.DoWorkFor (ID)? –

+0

@RoyiNamir: con este CTE 'ChildParent' - ¿no puedes hacer' SELECT * FROM ChildParent WHERE ID = 214' y obtener lo que necesitas ?? –

1

Todavía no tengo tiempo para analizar su pregunta y no estoy seguro si he entendido su problema o no, pero ¿no podría usar este SVF para obtener la ID del padre?

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 

, entonces debería ser capaz de encontrar a cada padre la parte superior de esta manera:

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

Gracias por responder. Tim el problema (como sabemos) que reduce el rendimiento (mientras se realizan llamadas en línea ...) es por eso que tiendo a evitar este tipo de solución. el problema (en general): tengo una tabla con 'id' y' parentId'. línea foreach en (seleccione * de ...) - necesito otra columna que tenga el valor superior de padre. He intentado con Cross Aply, pero el motor recursivo (CTE) no se puede usar con Cross Apply. –

+0

ive agregó una pantalla de impresión a la salida deseada. –

-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

explique su respuesta en lugar de solo dársela – ArtB

+0

Utilicé las uniones para obtener el paso al paso principal para el elemento secundario y luego introduje expresiones de tablas comunes más importantes en SQL Server 2005 que no están en el servidor 2000, por lo que se une para obtener valores, esta es una forma básica de obtener parentid para un valor – 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 

he mediante el sin expresión CTE y luego usando une a conseguir el paso a paso de los padres para el niño y luego más importantes expresiones de tabla común se introdujeron en SQL Server 2005 no en el servidor 2000 para el uso se une para obtener los valores de esta manera es básica para conseguir parentid por un valor menor

-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

infantil Esta solución no responde la pregunta. Los datos originales no tienen 'NULL' en' ParentID'. La consulta sugerida no produce el resultado deseado. –

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

Considere esta información de muestra y el SQL respectivo para acceder a los registros secundarios junto con su principal superior.

Sample DATA

código 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 
Cuestiones relacionadas