Pregunta

Tengo (simplificado para el ejemplo) una tabla con los siguientes datos

Row Start       Finish       ID  Amount
--- ---------   ----------   --  ------
  1 2008-10-01  2008-10-02   01      10
  2 2008-10-02  2008-10-03   02      20
  3 2008-10-03  2008-10-04   01      38
  4 2008-10-04  2008-10-05   01      23
  5 2008-10-05  2008-10-06   03      14
  6 2008-10-06  2008-10-07   02       3
  7 2008-10-07  2008-10-08   02       8
  8 2008-10-08  2008-11-08   03      19

Las fechas representan un período en el tiempo, la ID es el estado en el que se encontraba un sistema durante ese período y la cantidad es un valor relacionado con ese estado.

Lo que quiero hacer es agregar los Importes para las filas adyacentes con el número de ID igual , pero mantener la misma secuencia general para poder combinar las ejecuciones contiguas. Por eso quiero terminar con datos como:

Row Start       Finish       ID  Amount
--- ---------   ----------   --  ------
  1 2008-10-01  2008-10-02   01      10
  2 2008-10-02  2008-10-03   02      20
  3 2008-10-03  2008-10-05   01      61
  4 2008-10-05  2008-10-06   03      14
  5 2008-10-06  2008-10-08   02      11
  6 2008-10-08  2008-11-08   03      19

Estoy buscando una solución T-SQL que pueda incluirse en un SP, sin embargo, no puedo ver cómo hacerlo con consultas simples. Sospecho que puede requerir algún tipo de iteración, pero no quiero seguir ese camino.

La razón por la que quiero hacer esta agregación es que el siguiente paso en el proceso es hacer un SUM () y Count () agrupados por las ID únicas que se producen dentro de la secuencia, para que mis datos finales se vean como :

ID  Counts Total
--  ------ -----
01       2    71
02       2    31
03       2    33

Sin embargo si hago un simple

SELECT COUNT(ID), SUM(Amount) FROM data GROUP BY ID

En la tabla original obtengo algo como

ID  Counts Total
--  ------ -----
01       3    71
02       3    31
03       2    33

Lo que no es lo que quiero.

No hay solución correcta

Otros consejos

Si lees el libro " Desarrollo de aplicaciones de base de datos orientadas en el tiempo en SQL " por RT Snodgrass (el pdf está disponible en su sitio web en publicaciones), y llegue hasta la Figura 6.25 en p165-166, encontrará el SQL no trivial que se puede usar en el ejemplo actual para agrupar las distintas filas con el mismo valor de ID e intervalos de tiempo continuos.

El desarrollo de la consulta a continuación está cerca de ser correcto, pero hay un problema detectado justo al final, que tiene su origen en la primera instrucción SELECT. Todavía no he rastreado por qué se está dando la respuesta incorrecta. [Si alguien puede probar el SQL en su DBMS y decirme si la primera consulta funciona correctamente allí, ¡sería de gran ayuda!]

Se ve algo así como:

-- Derived from Figure 6.25 from Snodgrass "Developing Time-Oriented
-- Database Applications in SQL"
CREATE TABLE Data
(
    Start   DATE,
    Finish  DATE,
    ID      CHAR(2),
    Amount  INT
);

INSERT INTO Data VALUES('2008-10-01', '2008-10-02', '01', 10);
INSERT INTO Data VALUES('2008-10-02', '2008-10-03', '02', 20);
INSERT INTO Data VALUES('2008-10-03', '2008-10-04', '01', 38);
INSERT INTO Data VALUES('2008-10-04', '2008-10-05', '01', 23);
INSERT INTO Data VALUES('2008-10-05', '2008-10-06', '03', 14);
INSERT INTO Data VALUES('2008-10-06', '2008-10-07', '02',  3);
INSERT INTO Data VALUES('2008-10-07', '2008-10-08', '02',  8);
INSERT INTO Data VALUES('2008-10-08', '2008-11-08', '03', 19);

SELECT DISTINCT F.ID, F.Start, L.Finish
    FROM Data AS F, Data AS L
    WHERE F.Start < L.Finish
      AND F.ID = L.ID
      -- There are no gaps between F.Finish and L.Start
      AND NOT EXISTS (SELECT *
                        FROM Data AS M
                        WHERE M.ID = F.ID
                        AND F.Finish < M.Start
                        AND M.Start < L.Start
                        AND NOT EXISTS (SELECT *
                                            FROM Data AS T1
                                            WHERE T1.ID = F.ID
                                              AND T1.Start <  M.Start
                                              AND M.Start  <= T1.Finish))
      -- Cannot be extended further
      AND NOT EXISTS (SELECT *
                          FROM Data AS T2
                          WHERE T2.ID = F.ID
                            AND ((T2.Start <  F.Start  AND F.Start  <= T2.Finish)
                              OR (T2.Start <= L.Finish AND L.Finish <  T2.Finish)));

El resultado de esa consulta es:

01  2008-10-01      2008-10-02
01  2008-10-03      2008-10-05
02  2008-10-02      2008-10-03
02  2008-10-06      2008-10-08
03  2008-10-05      2008-10-06
03  2008-10-05      2008-11-08
03  2008-10-08      2008-11-08

Editado : hay un problema con la penúltima fila, no debería estar allí. Y no estoy claro (todavía) de dónde viene.

Ahora debemos tratar esa expresión compleja como una expresión de consulta en la cláusula FROM de otra instrucción SELECT, que sumará los valores de cantidad para un ID determinado sobre las entradas que se superponen con los rangos máximos que se muestran arriba.

SELECT M.ID, M.Start, M.Finish, SUM(D.Amount)
    FROM Data AS D,
         (SELECT DISTINCT F.ID, F.Start, L.Finish
              FROM Data AS F, Data AS L
              WHERE F.Start < L.Finish
                AND F.ID = L.ID
                -- There are no gaps between F.Finish and L.Start
                AND NOT EXISTS (SELECT *
                                    FROM Data AS M
                                    WHERE M.ID = F.ID
                                    AND F.Finish < M.Start
                                    AND M.Start < L.Start
                                    AND NOT EXISTS (SELECT *
                                                        FROM Data AS T1
                                                        WHERE T1.ID = F.ID
                                                          AND T1.Start <  M.Start
                                                          AND M.Start  <= T1.Finish))
                  -- Cannot be extended further
                AND NOT EXISTS (SELECT *
                                    FROM Data AS T2
                                    WHERE T2.ID = F.ID
                                      AND ((T2.Start <  F.Start  AND F.Start  <= T2.Finish)
                                        OR (T2.Start <= L.Finish AND L.Finish <  T2.Finish)))) AS M
    WHERE D.ID = M.ID
      AND M.Start  <= D.Start
      AND M.Finish >= D.Finish
    GROUP BY M.ID, M.Start, M.Finish
    ORDER BY M.ID, M.Start;

Esto da:

ID  Start        Finish       Amount
01  2008-10-01   2008-10-02   10
01  2008-10-03   2008-10-05   61
02  2008-10-02   2008-10-03   20
02  2008-10-06   2008-10-08   11
03  2008-10-05   2008-10-06   14
03  2008-10-05   2008-11-08   33              -- Here be trouble!
03  2008-10-08   2008-11-08   19

Editado : este es casi el conjunto de datos correcto en el que se realiza la agregación COUNT y SUM solicitada por la pregunta original, por lo que la respuesta final es:

SELECT I.ID, COUNT(*) AS Number, SUM(I.Amount) AS Amount
    FROM (SELECT M.ID, M.Start, M.Finish, SUM(D.Amount) AS Amount
            FROM Data AS D,
                 (SELECT DISTINCT F.ID, F.Start, L.Finish
                      FROM  Data AS F, Data AS L
                      WHERE F.Start < L.Finish
                        AND F.ID = L.ID
                        -- There are no gaps between F.Finish and L.Start
                        AND NOT EXISTS
                            (SELECT *
                                FROM  Data AS M
                                WHERE M.ID = F.ID
                                  AND F.Finish < M.Start
                                  AND M.Start < L.Start
                                  AND NOT EXISTS
                                      (SELECT *
                                          FROM Data AS T1
                                          WHERE T1.ID = F.ID
                                            AND T1.Start <  M.Start
                                            AND M.Start  <= T1.Finish))
                          -- Cannot be extended further
                        AND NOT EXISTS
                            (SELECT *
                                FROM  Data AS T2
                                WHERE T2.ID = F.ID
                                  AND ((T2.Start <  F.Start  AND F.Start  <= T2.Finish) OR
                                       (T2.Start <= L.Finish AND L.Finish <  T2.Finish)))
                 ) AS M
            WHERE D.ID = M.ID
              AND M.Start  <= D.Start
              AND M.Finish >= D.Finish
            GROUP BY M.ID, M.Start, M.Finish
          ) AS I
        GROUP BY I.ID
        ORDER BY I.ID;

id     number  amount
01      2      71
02      2      31
03      3      66

Revisión : Oh! Drat ... la entrada para 3 tiene el doble de la 'cantidad' que debería tener. Las partes 'editadas' anteriores indican donde las cosas empezaron a ir mal. Parece que la primera consulta es sutilmente incorrecta (tal vez está destinada a una pregunta diferente) o que el optimizador con el que estoy trabajando se está portando mal. Sin embargo, debe haber una respuesta estrechamente relacionada con esto que proporcione los valores correctos.

Para el registro: probado en IBM Informix Dynamic Server 11.50 en Solaris 10. Sin embargo, debería funcionar bien en cualquier otro DBMS SQL moderadamente conforme a la norma.

Probablemente necesite crear un cursor y recorrer los resultados, realizar un seguimiento de la identificación con la que está trabajando y acumular los datos a lo largo del camino. Cuando la identificación cambia, puede insertar los datos acumulados en una tabla temporal y devolver la tabla al final del procedimiento (seleccionar todo de ella). Una función basada en la tabla podría ser mejor, ya que puede insertarla en la tabla de retorno a medida que avanza.

  

Sospecho que puede requerir una iteración de algún tipo, pero no quiero seguir ese camino.

Creo que esa es la ruta que tendrás que tomar, usa un cursor para rellenar una variable de tabla. Si tiene una gran cantidad de registros, puede usar una tabla permanente para almacenar los resultados, entonces cuando necesite recuperar los datos, puede procesar solo los datos nuevos.

Agregaría un campo de bits con un valor predeterminado de 0 a la tabla de origen para realizar un seguimiento de los registros que se han procesado. Asumiendo que nadie está usando select * en la tabla, agregar una columna con un valor predeterminado no afectará el resto de su aplicación.

Agregue un comentario a esta publicación si desea ayuda para codificar la solución.

Bueno, decidí seguir la ruta de iteración usando una mezcla de combinaciones y cursores. Al unir la tabla de datos contra sí mismo, puedo crear una lista de enlaces de solo aquellos registros que son consecutivos.

INSERT INTO #CONSEC
  SELECT a.ID, a.Start, b.Finish, b.Amount 
  FROM Data a JOIN Data b 
  ON (a.Finish = b.Start) AND (a.ID = b.ID)

Luego puedo desenrollar la lista iterando sobre ella con un cursor y haciendo actualizaciones en la tabla de datos para ajustar (y eliminar los registros ahora extraños de la tabla de Datos)

DECLARE CCursor  CURSOR FOR
  SELECT ID, Start, Finish, Amount FROM #CONSEC ORDER BY Start DESC

@Total = 0
OPEN CCursor
FETCH NEXT FROM CCursor INTO @ID, @START, @FINISH, @AMOUNT
WHILE @FETCH_STATUS = 0
BEGIN
  @Total = @Total + @Amount
  @Start_Last = @Start
  @Finish_Last = @Finish
  @ID_Last = @ID

  DELETE FROM Data WHERE Start = @Finish
  FETCH NEXT FROM CCursor INTO @ID, @START, @FINISH, @AMOUNT
  IF (@ID_Last<> @ID) OR (@Finish<>@Start_Last)
    BEGIN
      UPDATE Data
        SET Amount = Amount + @Total
        WHERE Start = @Start_Last
      @Total = 0
    END  
END

CLOSE CCursor
DEALLOCATE CCursor

Todo esto funciona y tiene un rendimiento aceptable para los datos típicos que estoy usando.

Encontré un pequeño problema con el código anterior. Originalmente estaba actualizando la tabla de datos en cada bucle a través del cursor. Pero esto no funcionó. Parece que solo puede hacer una actualización en un registro, y que varias actualizaciones (para seguir agregando datos) vuelven a la lectura del contenido original del registro.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top