Domanda

Ho (semplificato per l'esempio) una tabella con i seguenti dati

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

Le date rappresentano un periodo di tempo, l'ID è lo stato in cui si trovava un sistema durante quel periodo e l'importo è un valore correlato a quello stato.

Quello che voglio fare è aggregare gli importi per file adiacenti con lo stesso numero ID stesso , ma mantenere la stessa sequenza generale in modo da poter combinare percorsi contigui. Quindi voglio finire con dati come:

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

Sto cercando una soluzione T-SQL che può essere inserita in un SP, tuttavia non riesco a vedere come farlo con semplici query. Sospetto che possa richiedere iterazioni di qualche tipo, ma non voglio seguire questa strada.

Il motivo per cui voglio fare questa aggregazione è che il prossimo passo nel processo è fare un SUM () e Count () raggruppati per gli ID univoci che si verificano all'interno della sequenza, in modo che i miei dati finali siano simili a :

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

Tuttavia, se faccio un semplice

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

Nella tabella originale ottengo qualcosa di simile

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

Che non è quello che voglio.

Nessuna soluzione corretta

Altri suggerimenti

Se leggi il libro " Sviluppo di applicazioni di database orientate al tempo in SQL " di RT Snodgrass (il cui pdf è disponibile sul suo sito web sotto pubblicazioni), e arrivando alla Figura 6.25 a p165-166, troverete l'SQL non banale che può essere usato nell'esempio corrente per raggruppare le varie righe con lo stesso valore ID e intervalli di tempo continui.

Lo sviluppo della query di seguito è quasi corretto, ma alla fine c'è un problema individuato che ha la sua origine nella prima istruzione SELECT. Non ho ancora rintracciato il motivo per cui viene data la risposta errata. [Se qualcuno può testare l'SQL sul proprio DBMS e dirmi se la prima query funziona correttamente lì, sarebbe di grande aiuto!]

Sembra qualcosa del tipo:

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

L'output di quella query è:

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

Modificato : c'è un problema con la penultima riga: non dovrebbe esserci. E non sono chiaro (ancora) da dove provenga.

Ora dobbiamo considerare quell'espressione complessa come un'espressione di query nella clausola FROM di un'altra istruzione SELECT, che sommerà i valori di quantità per un determinato ID sulle voci che si sovrappongono agli intervalli massimi mostrati sopra.

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;

Questo dà:

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

Modificato : questo è quasi il set di dati corretto su cui eseguire l'aggregazione COUNT e SUM richiesta dalla domanda originale, quindi la risposta finale è:

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

Recensione : Oh! Drat ... la voce per 3 ha il doppio dell '"importo" che dovrebbe avere. Le parti "modificate" precedenti indicano dove le cose hanno iniziato a non funzionare. Sembra che la prima query sia sottilmente sbagliata (forse è destinata a una domanda diversa) o che l'ottimizzatore con cui sto lavorando si sta comportando male. Tuttavia, dovrebbe esserci una risposta strettamente correlata a ciò che fornirà i valori corretti.

Per la cronaca: testato su IBM Informix Dynamic Server 11.50 su Solaris 10. Tuttavia, dovrebbe funzionare perfettamente su qualsiasi altro DBMS SQL moderatamente conforme agli standard.

Probabilmente devi creare un cursore e scorrere i risultati, tenendo traccia dell'id con cui stai lavorando e accumulando i dati lungo il percorso. Quando l'id cambia, è possibile inserire i dati accumulati in una tabella temporanea e restituire la tabella al termine della procedura (selezionare tutto da essa). Una funzione basata su tabella potrebbe essere migliore in quanto puoi semplicemente inserirla nella tabella di ritorno mentre procedi.

  

Sospetto che possa richiedere iterazioni di qualche tipo, ma non voglio seguire questa strada.

Penso che sia la strada da percorrere, usa un cursore per popolare una variabile di tabella. Se si dispone di un numero elevato di record, è possibile utilizzare una tabella permanente per archiviare i risultati, quindi quando è necessario recuperare i dati è possibile elaborare solo i nuovi dati.

Vorrei aggiungere un campo bit con un valore predefinito di 0 alla tabella di origine per tenere traccia di quali record sono stati elaborati. Supponendo che nessuno stia utilizzando select * nella tabella, l'aggiunta di una colonna con un valore predefinito non influirà sul resto dell'applicazione.

Aggiungi un commento a questo post se desideri aiuto per codificare la soluzione.

Bene, ho deciso di seguire la rotta dell'iterazione usando una combinazione di join e cursori. Unendo la tabella dei dati con se stesso posso creare un elenco di collegamenti solo di quei record che sono consecutivi.

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)

Quindi posso sbloccare l'elenco ripetendo su di esso con un cursore e facendo gli aggiornamenti alla tabella dei dati per regolare (ed eliminare i record ora estranei dalla tabella dei dati)

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

Tutto funziona e ha prestazioni accettabili per i dati tipici che sto usando.

Ho riscontrato un piccolo problema con il codice sopra. Inizialmente stavo aggiornando la tabella dei dati su ciascun loop attraverso il cursore. Ma questo non ha funzionato. Sembra che sia possibile eseguire un solo aggiornamento su un record e che più aggiornamenti (per continuare ad aggiungere dati) tornino alla lettura del contenuto originale del record.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top