sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Transponer filas y columnas (también conocido como pivote) solo con un COUNT() mínimo?

CASE

Si su caso es tan simple como se muestra, un CASE declaración hará:

SELECT year
     , sum(CASE WHEN animal = 'kittens' THEN price END) AS kittens
     , sum(CASE WHEN animal = 'puppies' THEN price END) AS puppies
FROM  (
   SELECT year, animal, avg(price) AS price
   FROM   tab_test
   GROUP  BY year, animal
   HAVING count(*) > 2
   ) t
GROUP  BY year
ORDER  BY year;

No importa si usa sum() , max() o min() como función agregada en la consulta externa. Todos resultan en el mismo valor en este caso.

SQL Fiddle

crosstab()

Con más categorías será más sencillo con una crosstab() consulta. Esto también debería ser más rápido para tablas más grandes .

Debe instalar el módulo adicional tablefunc (una vez por base de datos). Desde Postgres 9.1 eso es tan simple como:

CREATE EXTENSION tablefunc;

Detalles en esta respuesta relacionada:

SELECT * FROM crosstab(
      'SELECT year, animal, avg(price) AS price
       FROM   tab_test
       GROUP  BY animal, year
       HAVING count(*) > 2
       ORDER  BY 1,2'

      ,$$VALUES ('kittens'::text), ('puppies')$$)
AS ct ("year" text, "kittens" numeric, "puppies" numeric);

No hay sqlfiddle para este porque el sitio no permite módulos adicionales.

Valor de referencia

Para verificar mis afirmaciones, ejecuté un punto de referencia rápido con datos casi reales en mi pequeña base de datos de prueba. PostgreSQL 9.1.6. Prueba con EXPLAIN ANALYZE , al mejor de 10:

Configuración de prueba con 10020 filas:

CREATE TABLE tab_test (year int, animal text, price numeric);

-- years with lots of rows
INSERT INTO tab_test
SELECT 2000 + ((g + random() * 300))::int/1000 
     , CASE WHEN (g + (random() * 1.5)::int) %2 = 0 THEN 'kittens' ELSE 'puppies' END
     , (random() * 200)::numeric
FROM   generate_series(1,10000) g;

-- .. and some years with only few rows to include cases with count < 3
INSERT INTO tab_test
SELECT 2010 + ((g + random() * 10))::int/2
     , CASE WHEN (g + (random() * 1.5)::int) %2 = 0 THEN 'kittens' ELSE 'puppies' END
     , (random() * 200)::numeric
FROM   generate_series(1,20) g;

Resultados:

@bluefeet
Tiempo de ejecución total:95,401 ms

@wildplasser (diferentes resultados, incluye filas con count <= 3 )
Tiempo de ejecución total:64.497 ms

@Andreiy (+ ORDER BY )
&@Erwin1 - CASE (ambos funcionan casi igual)
Tiempo de ejecución total:39,105 ms

@Erwin2 - crosstab()
Tiempo de ejecución total:17,644 ms

Resultados en gran parte proporcionales (pero irrelevantes) con solo 20 filas. Solo el CTE de @wildplasser tiene más gastos generales y aumenta un poco.

Con más de un puñado de filas, crosstab() rápidamente toma la delantera. La consulta de @Andreiy funciona casi igual que mi versión simplificada, función agregada en SELECT externo (min() , max() , sum() ) no hace ninguna diferencia medible (solo dos filas por grupo).

Todo como se esperaba, sin sorpresas, tome mi configuración y pruébela @home.