AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 104596
Accepted
jpmc26
jpmc26
Asked: 2015-06-20 10:05:18 +0800 CST2015-06-20 10:05:18 +0800 CST 2015-06-20 10:05:18 +0800 CST

Soma cumulativa com valor de redefinição

  • 772

Considere a seguinte tabela:

ID | GROUP_ID | ORDER_VAL | RESET_VAL | VAL 
---+----------+-----------+-----------+-----
1  | 1        | 1         | (null)    | 3   
2  | 1        | 2         | (null)    | 2   
3  | 1        | 3         | (null)    | 1   
4  | 1        | 4         | 4         | 2   
5  | 1        | 5         | (null)    | 1   
6  | 2        | 1         | (null)    | 4   
7  | 2        | 2         | 2         | 3   
8  | 2        | 3         | (null)    | 4   
9  | 2        | 4         | (null)    | 2   
10 | 2        | 5         | (null)    | 2   
11 | 2        | 6         | (null)    | 4   
12 | 2        | 7         | 14        | 2   
13 | 2        | 8         | (null)    | 2   

Para cada linha, preciso calcular a soma cumulativa de VALtodas as linhas anteriores (ordenadas por ORDER_VALe agrupadas por GROUP_ID), mas cada vez que um não- NULL RESET_VALé encontrado, preciso usar esse valor para a soma. As linhas a seguir também precisam ser construídas em RESET_VALvez de usar a soma real. Observe que cada grupo pode ter vários valores de redefinição.

Este é o resultado que espero para a tabela acima:

ID | GROUP_ID | ORDER_VAL | RESET_VAL | VAL | CUMSUM
---+----------+-----------+-----------+-----+-------
1  | 1        | 1         | (null)    | 3   | 0
2  | 1        | 2         | (null)    | 2   | 3
3  | 1        | 3         | (null)    | 1   | 5
4  | 1        | 4         | 4         | 2   | 4
5  | 1        | 5         | (null)    | 1   | 6
6  | 2        | 1         | (null)    | 4   | 0
7  | 2        | 2         | 2         | 3   | 2
8  | 2        | 3         | (null)    | 4   | 5
9  | 2        | 4         | (null)    | 2   | 9
10 | 2        | 5         | (null)    | 2   | 11
11 | 2        | 6         | (null)    | 4   | 13
12 | 2        | 7         | 14        | 2   | 14
13 | 2        | 8         | (null)    | 2   | 16

Se não fosse pelos valores redefinidos, eu poderia usar uma consulta de janela:

SELECT temp.*,
       COALESCE(SUM(val) OVER (PARTITION BY group_id ORDER BY order_val ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING),
                0) AS cumsum
FROM temp;

SQLFiddle acima

Originalmente, pensei erroneamente que poderia apenas colocar RESET_VALno início do COALESCE, mas isso não funcionará, pois não redefinirá o valor das linhas seguintes.

Eu também tentei esta solução , mas ela redefine apenas para zero, não para um valor na coluna. Adaptá-lo para fazer isso não é trivial porque esse valor deve se propagar para todas as linhas seguintes.

Uma consulta recursiva parece um ajuste natural, mas ainda não consegui descobrir como fazer isso.

Provavelmente devo mencionar que a tabela com a qual realmente tenho que lidar é muito maior (centenas de milhares a alguns milhões de linhas) do que o exemplo acima, portanto, mencione se houver alguma armadilha de desempenho com qualquer resposta.

oracle oracle-11g-r2
  • 2 2 respostas
  • 8637 Views

2 respostas

  • Voted
  1. Best Answer
    ypercubeᵀᴹ
    2015-06-20T10:58:24+08:002015-06-20T10:58:24+08:00

    O seguinte funciona, mas provavelmente há uma versão mais inteligente. Explicação da lógica da consulta:

    Primeiro descobrimos quantas "reinicializações" foram feitas até e incluindo a linha atual contando os não nulos da reset_valcoluna, para que possamos separar as linhas em subgrupos.

    Também usamos outra função de janela LAST_VALUE()com IGNORE NULLS, para que possamos encontrar a última reset_value.

    Observe que ambas as funções de janela COUNT()e LAST_VALUE()têm um ORDER BY, portanto, a janela padrão ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Omitido na consulta, para deixar o código mais claro.

    Assumindo que valnão é anulável, a outra função de janela também pode ser encurtada, de:

           COALESCE(SUM(val) OVER 
             (PARTITION BY group_id, reset_count 
              ORDER BY order_val 
              ROWS BETWEEN UNBOUNDED PRECEDING 
                       AND 1 PRECEDING), 0)   
    

    (evitando o COALESCE()também) para:

           SUM(val) OVER 
             (PARTITION BY group_id, reset_count 
              ORDER BY order_val)
           - val
    

    Finalmente, no segundo cte, usamos os subgrupos encontrados acima (usando PARTITION BY group_id, reset_count) para encontrar as somas cumulativas.

    WITH x AS
      ( SELECT temp.*, 
               COUNT(reset_val) OVER 
                   (PARTITION BY group_id 
                    ORDER BY order_val)
                 AS reset_count,
               COALESCE(LAST_VALUE(reset_val IGNORE NULLS) OVER 
                   (PARTITION BY group_id 
                    ORDER BY order_val), 0)
                 AS reset_value
        FROM temp
      ) ,
    y AS 
      ( SELECT x.*,
               COALESCE(SUM(val) OVER 
                 (PARTITION BY group_id, reset_count 
                  ORDER BY order_val 
                  ROWS BETWEEN UNBOUNDED PRECEDING 
                           AND 1 PRECEDING), 0)            
               + reset_value AS cumsum      
        FROM x
      )
    SELECT *
    FROM y ;
    

    Teste no SQLfiddle .


    Outra variação, baseada na resposta recursiva de @Chris . (ligeiramente melhorado, trabalha com não consecutivos order_val, evita o final GROUP BY).
    Também funciona caso a primeira linha de um grupo tenha reset_val:

    WITH row_nums AS
      ( SELECT id, group_id, order_val, reset_val, val, 
               ROW_NUMBER() OVER (PARTITION BY group_id
                                  ORDER BY order_val)
                 AS rn
        FROM temp
      ) ,
    updated_temp (id, group_id, order_val, reset_val, val, rn, cumsum) AS
      ( SELECT id, group_id, order_val, reset_val, val, rn, 
               COALESCE(reset_val, 0)
        FROM row_nums
        WHERE rn = 1
      UNION ALL
        SELECT curr.id, curr.group_id, curr.order_val, curr.reset_val, curr.val, curr.rn, 
               COALESCE(curr.reset_val, prev.val + prev.cumsum) 
        FROM row_nums  curr 
          JOIN updated_temp  prev 
            ON  curr.rn-1 = prev.rn 
            AND curr.group_id = prev.group_id
      )
    SELECT id, group_id, order_val, reset_val, val, cumsum
    FROM updated_temp
    ORDER BY group_id, order_val ;
    

    Teste no SQLfiddle-2 .


    Mais uma variação, usando a CONNECT BYsintaxe mais antiga (proprietária) para consultas recursivas. Mais compacto, mas acho mais difícil de escrever e ler do que a versão CTE:

    WITH row_nums AS
      ( SELECT id, group_id, order_val, reset_val, val, 
               ROW_NUMBER() OVER (PARTITION BY group_id
                                  ORDER BY order_val)
                 AS rn,
               COALESCE(reset_val, 0) AS cumsum
        FROM temp
      ) 
    SELECT id, group_id, order_val, reset_val, val, rn,  
           COALESCE(reset_val, PRIOR val + PRIOR cumsum, 0) AS cumsum
    FROM row_nums
    START WITH rn = 1 OR reset_val IS NOT NULL
    CONNECT BY  rn-1 = PRIOR rn 
            AND group_id = PRIOR group_id
            AND reset_val IS NULL 
    ORDER BY group_id, order_val ; 
    

    Testado em SQLfiddle-3 .

    • 4
  2. Chris
    2015-06-20T12:31:23+08:002015-06-20T12:31:23+08:00

    Como exercício acadêmico, implementei uma solução em Postgres. Agora, eu sei que era uma pergunta sobre Oracle, mas também foi enquadrada como uma pergunta de SQL! :) Se alguém estiver mais familiarizado com a sintaxe de consulta recursiva do Oracle, talvez encontre as alterações necessárias para fazer isso rodar no Oracle.

    Consulta recursiva (no Postgres)

    Nesta solução, faço uso de consultas recursivas, aplicando a sintaxe do Postgres WITH RECURSIVEno meu CTE. A consulta recursiva é a seguinte:

    WITH RECURSIVE updated_temp AS (
      SELECT temp.*, 0::numeric AS CUMSUM FROM temp
      UNION
      SELECT curr.id, curr.group_id, curr.order_val, curr.reset_val, curr.val, 
      COALESCE(curr.RESET_VAL,prev.VAL + prev.CUMSUM) AS CUMSUM
      FROM temp AS curr JOIN updated_temp AS prev 
      ON curr.ORDER_VAL-1 = prev.ORDER_VAL AND curr.group_id = prev.group_id)
    SELECT id, group_id, order_val, reset_val, val, max(cumsum) FROM updated_temp 
    GROUP BY id, group_id, order_val, reset_val, val ORDER BY id ASC;
    

    Aqui está o SQL Fiddle correspondente .

    Obviamente, a chave para o comportamento adequado dessa consulta é primeiro usar a parte recursiva da consulta para criar sua soma cumulativa, certificando-se de usar COALESCEpara substituir em qualquer um RESET_VALque determine o valor CUMSUMda linha. O último SELECTsimplesmente faz com que você filtre pelo valor máximo do CUMSUMporque se a consulta foi construída corretamente, o máximo é idêntico à soma cumulativa.

    Editar: versão Oracle da consulta recursiva

    Esta tradução para Oracle foi fornecida pelo ypercube :

    WITH updated_temp (id, group_id, order_val, reset_val, val, cumsum) 
    AS (
      SELECT id, group_id, order_val, reset_val, val, 0 
      FROM temp
      UNION ALL
      SELECT curr.id, curr.group_id, curr.order_val, curr.reset_val, curr.val, 
         COALESCE(curr.reset_val, prev.val + prev.cumsum) 
      FROM temp  curr 
        JOIN updated_temp  prev 
          ON  curr.order_val-1 = prev.order_val 
          AND curr.group_id = prev.group_id
    )
      SELECT id, group_id, order_val, reset_val, val, max(cumsum) 
      FROM updated_temp 
      GROUP BY id, group_id, order_val, reset_val, val 
      ORDER BY id ASC;
    

    Oracle SQLFiddle

    • 4

relate perguntas

  • Backups de banco de dados no Oracle - Exportar o banco de dados ou usar outras ferramentas?

  • ORDER BY usando prioridades personalizadas para colunas de texto

  • Interface sqlplus confortável? [fechado]

  • Como encontrar as instruções SQL mais recentes no banco de dados?

  • Como posso consultar nomes usando expressões regulares?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve