PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Trasporre righe e colonne (aka pivot) solo con un minimo COUNT()?

CASE

Se il tuo caso è semplice come illustrato, un CASE la dichiarazione farà:

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;

Non importa se usi sum() , max() o min() come funzione aggregata nella query esterna. In questo caso danno tutti lo stesso valore.

SQL Fiddle

crosstab()

Con più categorie sarà più semplice con un crosstab() interrogazione. Questo dovrebbe anche essere più veloce per i tavoli più grandi .

È necessario installare il modulo aggiuntivo tablefunc (una volta per database). Da Postgres 9.1 è semplice come:

CREATE EXTENSION tablefunc;

Dettagli in questa risposta correlata:

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

Nessun sqlfiddle per questo perché il sito non consente moduli aggiuntivi.

Parametro

Per verificare le mie affermazioni, ho eseguito un rapido benchmark con dati quasi reali nel mio piccolo database di test. PostgreSQL 9.1.6. Prova con EXPLAIN ANALYZE , al meglio di 10:

Configurazione di prova con 10020 righe:

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;

Risultati:

@bluefeet
Durata totale:95.401 ms

@wildplasser (risultati diversi, include righe con count <= 3 )
Durata totale:64.497 ms

@Andreiy (+ ORDER BY )
&@Erwin1 - CASE (entrambi funzionano più o meno allo stesso modo)
Durata totale:39.105 ms

@Erwin2 - crosstab()
Durata totale:17.644 ms

Risultati ampiamente proporzionali (ma irrilevanti) con solo 20 righe. Solo il CTE di @wildplasser ha più sovraccarico e picchi leggermente.

Con più di una manciata di righe, crosstab() prende rapidamente il comando.@La query di Andreiy funziona più o meno come la mia versione semplificata, funzione di aggregazione in esterno SELECT (min() , max() , sum() ) non fa alcuna differenza misurabile (solo due righe per gruppo).

Tutto come previsto, nessuna sorpresa, prendi il mio setup e provalo a casa.