Oracle SQL Query para resumir estadísticas, usando GROUP BY
Pregunta
Tengo una tabla Oracle con datos que se ven así:
ID BATCH STATUS
1 1 0
2 1 0
3 1 1
4 2 0
Es decir, ID es la clave principal, habrá varias filas para cada " lote " y cada fila tendrá un código de estado en la columna ESTADO . Hay muchas otras columnas, pero estas son las importantes.
Necesito escribir una consulta que resuma los códigos de estado para cada lote ; Hay tres valores posibles que pueden ir en la columna ESTADO, 0, 1 y 2, y me gustaría una salida similar a esta:
BATCH STATUS0 STATUS1 STATUS2
1 2 1 0
2 1 0 0
Esos números serían cuentas; para el lote 1, hay
- 2 registros donde STATUS se establece en 0
- 1 registro donde STATUS se establece en 1 y
- no hay registros donde STATUS se establece en 0.
Para el lote 2, hay
- 1 registro donde STATUS se establece en 0 y
- no hay registros donde STATUS se establece en 1 o 2.
¿Hay alguna manera de hacer esto en una consulta, sin tener que volver a escribir la consulta para cada código de estado? es decir, puedo escribir fácilmente una consulta como esta y ejecutarla tres veces:
SELECT batch, COUNT(status)
FROM table
WHERE status = 0
GROUP BY batch
Podría ejecutar eso, luego ejecutarlo nuevamente donde status = 1, y nuevamente donde status = 2, pero espero hacerlo en una consulta.
Si hace la diferencia, aparte de la columna ESTADO hay una columna otra que podría resumir de la misma manera, otra razón por la que no desea tener que ejecutar la instrucción SELECT después de la instrucción SELECT y amalgamar todos los resultados.
Solución
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;
Esto a menudo se llama " pivote " consulta, y he escrito un artículo sobre cómo generar estas consultas dinámicamente en mi blog .
Versión que usa DECODE (específica de Oracle pero menos detallada):
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;
Otros consejos
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 pregunta si hay algún beneficio de rendimiento de un enfoque (SUM) sobre el otro (COUNT). La ejecución de una prueba simple en una tabla con 26K filas muestra que el enfoque COUNT es significativamente más 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;
/
Esto produjo el siguiente 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
Repetí la prueba un par de veces para eliminar los efectos del almacenamiento en caché. También cambié las declaraciones select. Los resultados fueron similares en todos los ámbitos.
EDITAR: este es el mismo arnés de prueba que usé para responder una pregunta similar con.