PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Transposer les lignes et les colonnes (a.k.a. pivot) uniquement avec un minimum COUNT() ?

CASE

Si votre cas est aussi simple que démontré, un CASE déclaration fera :

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;

Peu importe que vous utilisiez sum() , max() ou min() en tant que fonction d'agrégation dans la requête externe. Ils donnent tous la même valeur dans ce cas.

SQL Fiddle

crosstab()

Avec plus de catégories ce sera plus simple avec un crosstab() requête. Cela devrait également être plus rapide pour les grandes tables .

Vous devez installer le module supplémentaire tablefunc (une fois par base de données). Depuis Postgres 9.1, c'est aussi simple que :

CREATE EXTENSION tablefunc;

Détails dans cette réponse connexe :

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

Pas de sqlfiddle pour celui-ci car le site n'autorise pas les modules supplémentaires.

Référence

Pour vérifier mes affirmations, j'ai exécuté un benchmark rapide avec des données proches de la réalité dans ma petite base de données de test. PostgreSQL 9.1.6. Testez avec EXPLAIN ANALYZE , meilleur des 10 :

Configuration du test avec 10 020 lignes :

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;

Résultats :

@bluefeet
Autonomie totale :95,401 ms

@wildplasser (différents résultats, inclut des lignes avec count <= 3 )
Autonomie totale :64,497 ms

@Andreiy (+ ORDER BY )
&@Erwin1 - CASE (les deux fonctionnent à peu près de la même manière)
Durée d'exécution totale :39,105 ms

@Erwin2 - crosstab()
Autonomie totale :17,644 ms

Résultats largement proportionnels (mais non pertinents) avec seulement 20 lignes. Seul le CTE de @wildplasser a plus de frais généraux et un peu de pics.

Avec plus d'une poignée de lignes, crosstab() prend rapidement les devants. La requête de @Andreiy fonctionne à peu près de la même manière que ma version simplifiée, fonction d'agrégation dans SELECT externe (min() , max() , sum() ) ne fait aucune différence mesurable (seulement deux lignes par groupe).

Tout comme prévu, pas de surprises, prenez ma configuration et essayez-la à la maison.