PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Zeilen und Spalten (alias Pivot) nur mit einem Minimum von COUNT() transponieren?

CASE

Wenn Ihr Fall so einfach ist wie gezeigt, ein CASE Anweisung reicht:

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;

Dabei spielt es keine Rolle, ob Sie sum() verwenden , max() oder min() als Aggregatfunktion in der äußeren Abfrage. Sie alle ergeben in diesem Fall den gleichen Wert.

SQL-Fiddle

crosstab()

Bei mehr Kategorien wird es mit einer crosstab() einfacher Anfrage. Dies sollte auch schneller für größere Tabellen sein .

Sie müssen das zusätzliche Modul tablefunc installieren (einmal pro Datenbank). Seit Postgres 9.1 ist das so einfach wie:

CREATE EXTENSION tablefunc;

Details in dieser verwandten Antwort:

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

Kein sqlfiddle für dieses hier, da die Site keine zusätzlichen Module zulässt.

Benchmark

Um meine Behauptungen zu überprüfen, habe ich in meiner kleinen Testdatenbank einen schnellen Benchmark mit nahezu realen Daten durchgeführt. PostgreSQL 9.1.6. Testen Sie mit EXPLAIN ANALYZE , Beste von 10:

Testaufbau mit 10020 Zeilen:

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;

Ergebnisse:

@bluefeet
Gesamtlaufzeit:95.401 ms

@wildplasser (unterschiedliche Ergebnisse, enthält Zeilen mit count <= 3 )
Gesamtlaufzeit:64,497 ms

@Andreiy (+ ORDER BY )
&@Erwin1 - CASE (beide Leistung ungefähr gleich)
Gesamtlaufzeit:39,105 ms

@Erwin2 - crosstab()
Gesamtlaufzeit:17,644 ms

Weitgehend proportionale (aber irrelevante) Ergebnisse mit nur 20 Zeilen. Nur der CTE von @wildplasser hat mehr Overhead und Spitzen.

Bei mehr als einer Handvoll Zeilen crosstab() übernimmt schnell die Führung. Die Abfrage von @Andreiy funktioniert ungefähr genauso wie meine vereinfachte Version, Aggregatfunktion im äußeren SELECT (min() , max() , sum() ) macht keinen messbaren Unterschied (nur zwei Zeilen pro Gruppe).

Alles wie erwartet, keine Überraschungen, nimm mein Setup und probiere es @home aus.