2012-07-10 16 views
5

necesito hacer una suma recursiva en SQL Server. Quiero un procedimiento almacenado donde pueda pasar un ID padre, luego devolver un total para todos los hijos (y los hijos de los niños) vinculados a ese ID padre.Recursiva SUM Sql Server

Esto es lo que tengo hasta ahora

IF object_id('tempdb..#Averages') IS NOT NULL 
BEGIN 
    DROP TABLE #Averages 
END 


CREATE TABLE #Averages 
(
ID INT PRIMARY KEY CLUSTERED IDENTITY(1,1), 
Name VARCHAR(255), 
ParentID int, 
Value INT 
) 

INSERT INTO #Averages(Name,ParentID,Value)VALUES('Fred',NULL,1) 
INSERT INTO #Averages(Name,ParentID,Value)VALUES('Bets',NULL,1) 

INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Wynand',ID,21 FROM #Averages WHERE  Name = 'Fred' ) 

INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Dewald',ID,27 FROM #Averages WHERE  Name = 'Fred' ) 
INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Katelynn',ID,1 FROM #Averages WHERE Name = 'Dewald' ) 

INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Jacques',ID,28 FROM #Averages WHERE Name = 'Bets' ) 
INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Luan',ID,4 FROM #Averages WHERE Name = 'Jacques' ) 
INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Ruben',ID,2 FROM #Averages WHERE Name = 'Jacques' ) 


;WITH Personal AS 
(
SELECT N=1, ID,Name,ParentID,Value 
FROM #Averages 
WHERE ParentID IS NULL 
UNION ALL 
SELECT N+1, Av.ID,Av.Name,Av.ParentID,Av.Value 
FROM #Averages Av 
INNER JOIN Personal P ON P.ID = Av.ParentID 
) 

SELECT Name, 
    SUM(Value) as Total 
FROM Personal 
WHERE N<=3 
GROUP BY Name 
+2

@alfasin - ¿No estás seguro de por qué sigues mencionando a MySQL en preguntas etiquetadas SQL Server? –

+0

@MartinSmith demasiado tarde ... demasiado cansado ... :) borrar el comentario como no constructivo – alfasin

+0

debe haber sido un error tipográfico. sory :-) – Captain0

Respuesta

2

Después de jugar un poco, creo que lo tengo. Agregué una identificación de nivel superior, que establecí en la raíz para el CTE. Y luego solo agrega el ID de nivel superior para toda la recursión.

Al final solo sumamos, y básicamente usamos TopLevelId para unirnos a la Tabla de nivel superior.

IF object_id('tempdb..#Averages') IS NOT NULL 
BEGIN 
    DROP TABLE #Averages 
END 

CREATE TABLE #Averages 
(
    ID INT PRIMARY KEY CLUSTERED IDENTITY(1,1), 
    Name VARCHAR(255), 
    ParentID int, 
    Value INT 
) 

INSERT INTO #Averages(Name,ParentID,Value)VALUES('Fred',NULL,1) 
INSERT INTO #Averages(Name,ParentID,Value)VALUES('Bets',NULL,1) 

INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Wynand',ID,21 FROM #Averages WHERE Name = 'Fred' ) 

INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Dewald',ID,27 FROM #Averages WHERE Name = 'Fred' ) 
INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Katelynn',ID,1 FROM #Averages WHERE Name = 'Dewald' ) 

INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Jacques',ID,28 FROM #Averages WHERE Name = 'Bets' ) 
INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Luan',ID,4 FROM #Averages WHERE Name = 'Jacques' ) 
INSERT INTO #Averages(Name,ParentID,Value)(SELECT 'Ruben',ID,2 FROM #Averages WHERE Name = 'Jacques' ) 


;WITH Personal AS 
(
    SELECT N=1, 
     ID, 
     Name, 
     ParentID, 
     Value, 
     TopLevelID =ID 
    FROM #Averages 
    WHERE ParentID IS NULL 

    UNION ALL 

    SELECT N+1, 
     Av.ID, 
     Av.Name, 
     Av.ParentID, 
     Av.Value, 
     TopLevelID =P.TopLevelID 
    FROM #Averages Av 
    INNER JOIN Personal P ON P.ID = Av.ParentID 
) 

SELECT SUM(P.Value) AS Total, 
     A.Name 
FROM Personal P 
INNER JOIN #Averages A on A.ID = P.TopLevelID 
GROUP BY A.Name 
+0

Respuesta muy buena y clara donde "Personal" es la tabla CTE como en los ejemplos de Microsoft. ¡Gracias! –

3

Ésta es una forma de conseguir lo que quiere, aunque es ligeramente diferente al enfoque que tiene arriba:

SQLFiddle

Create Table #Ancestors (
    ID int 
    , Name VARCHAR(255) 
    , ParentID int 
    , AncestryCompleteTF tinyint 
    , Ancestors varchar(max) 
    , TotalValue int  
) 

INSERT INTO #Ancestors 
SELECT 
    ID 
    , Name 
    , ParentID 
    , CASE ISNULL(ParentID, 0) 
    WHEN 0 THEN 1 
    ELSE 0 
    END 
    , CONVERT(VARCHAR, ISNULL(ParentID, '')) 
    , Value 
FROM 
    Averages 

WHILE EXISTS (SELECT * FROM #Ancestors WHERE AncestryCompleteTF = 0) 
BEGIN 
    UPDATE C SET 
    C.Ancestors = P.Ancestors + ',' + CONVERT(VARCHAR, P.ID), 
    C.AncestryCompleteTF = 1, 
    C.TotalValue = P.TotalValue + C.TotalValue 
    FROM #Ancestors C 
    INNER JOIN #Ancestors P ON (C.ParentID = P.ID) 
    AND P.AncestryCompleteTF = 1 
END 

SELECT 
    Name 
, TotalValue 
FROM 
    #Ancestors 

Básicamente creo una tabla temporal, y uso un ciclo while para seguir actualizando los totales de las filas donde los padres ya han sido calculados (ya que este es solo un caso de anuncio ding el total de la fila actual al total de la fila principal) hasta que se hayan calculado todas las filas. Las filas donde ParentID es nulo se configuran como completadas para comenzar, por lo que sus descendientes directos se calcularán primero, y luego los descendientes de esas filas, etc.

+1

Los violines son buenos para crear ejemplos para que las personas puedan responder preguntas, pero por lo general es preferible tener al menos la consulta de la solución real (si no la configuración del esquema/datos) en tu respuesta. –

+0

@Damien_The_Unbeliever - de acuerdo. Ahora pegado. – soupy1976