Pergunta

Eu tenho uma tabela Oracle com dados que se parece com isso:

ID   BATCH   STATUS
1    1       0
2    1       0
3    1       1
4    2       0

Isto é, ID é a chave primária, haverá várias linhas para cada "lote", e cada linha terá um código de status na STATUS coluna. Há um monte de outras colunas, mas estes são os mais importantes.

Eu preciso escrever uma consulta que irá resumir os códigos estado para cada lote ; Existem três valores possíveis que podem ir na coluna STATUS, 0, 1 e 2, e eu gostaria de saída que é algo como isto:

BATCH  STATUS0  STATUS1  STATUS2
1      2        1        0
2      1        0        0

Esses números seriam contagem; para o lote 1, há

  • 2 registros onde STATUS está definido para 0
  • 1 registro onde STATUS é definido como 1, e
  • nenhum registro onde STATUS está definido para 0.

Por lote 2, há

  • 1 registro onde STATUS é definido como 0, e
  • nenhum registro onde STATUS está definido para 1 ou 2.

Existe uma maneira que eu posso fazer isso em uma consulta, sem ter que reescrever a consulta para cada código de status? ou seja, eu posso facilmente escrever uma consulta como esta, e executá-lo três vezes:

SELECT batch, COUNT(status)
FROM table
WHERE status = 0
GROUP BY batch

eu poderia correr que, em seguida, executá-lo novamente onde status = 1, e novamente onde status = 2, mas eu estou esperando para fazê-lo em uma consulta.

Se ele faz a diferença, além do STATUS coluna há outro coluna que eu poderia querer resumir da mesma forma - outra razão que eu não faço quero ter que executar instrução SELECT após a instrução SELECT e amalgamar todos os resultados.

Foi útil?

Solução

select batch 
,      count(case when status=1 then 1 end) status1
,      count(case when status=2 then 1 end) status2
,      count(case when status=3 then 1 end) status3
from   table
group by batch;

Isso é muitas vezes chamado de uma consulta "pivot", e eu escrevi um artigo sobre como gerar essas consultas dinamicamente no meu blog .

(-específica Oracle, mas menos detalhada) Versão usando DECODE:

select batch 
,      count(decode(status,1,1)) status1
,      count(decode(status,2,1)) status2
,      count(decode(status,3,1)) status3
from   table
group by batch;

Outras dicas

select batch,
sum(select case when status = 0 then 1 else 0 end) status0,
sum(select case when status = 1 then 1 else 0 end) status1,
sum(select case when status = 2 then 1 else 0 end) status2
from table
group by batch
select batch,
sum((decode(status,0,1,0)) status0,
sum((decode(status,1,1,0)) status1,
sum((decode(status,2,1,0)) status2,
from table
group by batch

OP pergunta se há qualquer benefício de uma abordagem (SUM) sobre o outro (COUNT) desempenho. Execução de um teste simpleminded em uma tabela com 26k linhas mostra que a abordagem de contagem é significativamente mais rápido. YMMV.

DECLARE
  CURSOR B IS
     select batch_id
       FROM batch
      WHERE ROWNUM < 2000;

  v_t1  NUMBER;
  v_t2  NUMBER;
  v_c1  NUMBER;
  v_c2  NUMBER;
  v_opn INTEGER;
  v_cls INTEGER;
  v_btc VARCHAR2(100);
BEGIN
-- Loop using SUM
  v_t1 := dbms_utility.get_time;
  v_c1 := dbms_utility.get_cpu_time;
  FOR R IN B LOOP
     FOR R2 IN (SELECT batch_type_code
                     , SUM(decode(batch_status_code, 'CLOSED', 1, 0)) closed
                     , SUM(decode(batch_status_code, 'OPEN', 1, 0)) OPEN
                     , SUM(decode(batch_status_code, 'REWORK', 1, 0)) rework
                  FROM batch
                 GROUP BY batch_type_code) LOOP 
        v_opn := R2.open;
        v_cls := R2.closed;
     END LOOP;
  END LOOP;
  v_t2 := dbms_utility.get_time;
  v_c2 := dbms_utility.get_cpu_time;
  dbms_output.put_line('For loop using SUM:');
  dbms_output.put_line('CPU seconds used: '||(v_c2 - v_c1)/100);
  dbms_output.put_line('Elapsed time: '||(v_t2 - v_t1)/100);

-- Loop using COUNT
  v_t1 := dbms_utility.get_time;
  v_c1 := dbms_utility.get_cpu_time;
  FOR R IN B LOOP
     FOR R2 IN (SELECT batch_type_code
                     , COUNT(CASE WHEN batch_status_code = 'CLOSED' THEN 1 END) closed
                     , COUNT(CASE WHEN batch_status_code = 'OPEN' THEN 1 END) OPEN
                     , COUNT(CASE WHEN batch_status_code = 'REWORK' THEN 1 END) rework
                  FROM batch
                 GROUP BY batch_type_code) LOOP 
        v_opn := R2.open;
        v_cls := R2.closed;
     END LOOP;
  END LOOP;
  v_t2 := dbms_utility.get_time;
  v_c2 := dbms_utility.get_cpu_time;
  dbms_output.put_line('For loop using COUNT:');
  dbms_output.put_line('CPU seconds used: '||(v_c2 - v_c1)/100);
  dbms_output.put_line('Elapsed time: '||(v_t2 - v_t1)/100);
END;
/

Isto rendeu o seguinte resultado:

For loop using SUM:
CPU seconds used: 40
Elapsed time: 40.09
For loop using COUNT:
CPU seconds used: 33.26
Elapsed time: 33.34

Eu repeti o teste de um par de vezes para eliminar quaisquer efeitos de cache. Eu também troquei as instruções select. Os resultados foram semelhantes em toda a linha.

EDIT: este é o mesmo equipamento de teste Eu costumava responder uma pergunta semelhante com.

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