Pergunta

Eu tenho (simplificado para o exemplo) uma tabela com os seguintes dados

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

As datas representam um período de tempo, o ID é o estado de um sistema estava durante esse período e o montante é um valor relacionado a esse estado.

O que eu quero fazer é agregar os montantes para adjacentes linhas com o mesma número de identificação, mas manter a mesma seqüência geral de modo que contíguas execuções podem ser combinados. Assim, eu quero acabar com dados 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

Eu sou depois uma solução de T-SQL que pode ser colocado em um SP, no entanto eu não posso ver como fazer isso com consultas simples. Eu suspeito que ele pode exigir iteração de algum tipo, mas eu não quero ir por esse caminho.

A razão que eu quero fazer essa agregação é que o próximo passo no processo é fazer um SUM () e Contagem () agrupados pelos de identificação única que ocorrem dentro da seqüência, de modo que os meus dados finais será algo parecido :

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

No entanto, se eu faço uma simples

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

Na tabela original eu obter algo como

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

O que não é o que eu quero.

Nenhuma solução correta

Outras dicas

Se você ler o livro "Desenvolvendo Time-Oriented Applications banco de dados SQL" por RT Snodgrass (o pdf do que está disponível no seu web site sob publicações), e chegar tão longe como Figura 6.25 em p165-166, você vai encontrar o SQL não-trivial que pode ser usado no exemplo atual para agrupar os diversos linhas com o mesmo valor de ID e os intervalos de tempo contínuo.

O desenvolvimento consulta abaixo está perto de correto, mas há um problema visto à direita no final, que tem a sua fonte na primeira instrução SELECT. Eu ainda não rastreou por isso que a resposta incorreta está sendo dada. [Se alguém pode testar o SQL em seus DBMS e me diga se a primeira consulta funciona corretamente lá, seria uma grande ajuda!]

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

A saída dessa consulta é:

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 : Há um problema com a penúltima linha - não deveria estar lá. E eu não sou claro (ainda) onde ele está vindo.

Agora, precisamos tratar essa expressão complexa como uma expressão de consulta na cláusula FROM de outra instrução SELECT, que irá somar os valores de quantidade para um determinado ID sobre as entradas que se sobrepõem com a máxima margens indicadas acima.

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;

Isto 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

Editado : Este é quase do conjunto de dados correta em que para fazer a contagem e agregação SUM solicitado com a pergunta original, então a resposta final é:

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

revisão : Oh! Drat ... a entrada para 3 tem duas vezes o 'valor' que deveria ter. partes anteriores 'editada' indicam onde as coisas começaram a dar errado. Parece que quer a primeira consulta é sutilmente errado (talvez que se destine a uma pergunta diferente), ou o otimizador Eu estou trabalhando com está se comportando mal. No entanto, deve haver uma resposta estreitamente relacionado com esse que vai dar os valores corretos.

Para o registro:. Testado em IBM Informix Dynamic Server 11.50 no Solaris 10. No entanto, deve funcionar bem em qualquer outro SQL DBMS moderadamente padrão-conformant

Provavelmente precisa criar um cursor e percorrer os resultados, mantendo o controle de qual id você está trabalhando e acumulando os dados ao longo do caminho. Quando o ID de mudanças que você pode inserir os dados acumulados em uma tabela temporária e retornar a tabela no final do procedimento (selecionar tudo a partir dele). Uma função baseada em tabela poderia ser melhor como você pode em seguida, basta inserir na tabela o retorno que você vá junto.

Eu suspeito que ele pode exigir iteração de algum tipo, mas eu não quero ir por esse caminho.

Eu acho que é a rota que você terá que tomar, usar um cursor para preencher uma variável de tabela. Se você tem um grande número de registros que você pode usar uma tabela permanente para armazenar os resultados, em seguida, quando você precisa para recuperar os dados que você poderia processar apenas os novos dados.

Gostaria de acrescentar um campo bit com um padrão de 0 a tabela de origem para manter o controle de quais registros foram processados. Supondo que ninguém está usando select * sobre a mesa, adicionando uma coluna com um valor padrão não afetará o resto de sua aplicação.

Adicionar um comentário a este post se você quiser ajudar a codificação da solução.

Bem, eu decidi ir abaixo da rota iteração usando uma mistura de junta e cursores. Juntando-se a tabela de dados contra si mesmo posso criar uma lista de links de apenas os registos que são 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)

Então eu posso descansar a lista por iteração sobre ele com um cursor, e fazendo atualizações de volta para a tabela de dados para ajustar (e excluir os registros agora estranhos da tabela de dados a)

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

Esta todas as obras e tem um desempenho aceitável para os dados típicos que estou usando.

Eu fiz encontrar um pequeno problema com o código acima. Inicialmente eu estava atualizando a tabela de dados do em cada loop através do cursor. Mas isso não funcionou. Parece que você só pode fazer uma atualização em um registro, e que várias atualizações (em ordem para continuar a acrescentar dados) reverter para a leitura dos conteúdos originais do registro.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top