sql >> Database >  >> RDS >> PostgreSQL

Rijen en kolommen (ook wel pivot genoemd) alleen transponeren met een minimum AANTAL()?

CASE

Als uw zaak zo eenvoudig is als aangetoond, een CASE verklaring zal doen:

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;

Het maakt niet uit of je sum() . gebruikt , max() of min() als aggregatiefunctie in de buitenste query. In dit geval resulteren ze allemaal in dezelfde waarde.

SQL Fiddle

crosstab()

Met meer categorieën zal het eenvoudiger zijn met een crosstab() vraag. Dit zou ook sneller moeten zijn voor grotere tafels .

U moet de extra module tablefunc installeren (eenmaal per databank). Sinds Postgres 9.1 is dat zo simpel als:

CREATE EXTENSION tablefunc;

Details in dit gerelateerde antwoord:

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

Geen sqlfiddle voor deze omdat de site geen extra modules toestaat.

Benchmark

Om mijn beweringen te verifiëren, heb ik een snelle benchmark uitgevoerd met bijna echte gegevens in mijn kleine testdatabase. PostgreSQL 9.1.6. Test met EXPLAIN ANALYZE , beste van 10:

Testopstelling met 10020 rijen:

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;

Resultaten:

@bluefeet
Totale looptijd:95,401 ms

@wildplasser (verschillende resultaten, inclusief rijen met count <= 3 )
Totale looptijd:64,497 ms

@Andreiy (+ ORDER BY )
&@Erwin1 - CASE (beiden presteren ongeveer hetzelfde)
Totale looptijd:39,105 ms

@Erwin2 - crosstab()
Totale looptijd:17,644 ms

Grotendeels proportionele (maar irrelevante) resultaten met slechts 20 rijen. Alleen de CTE van @wildplasser heeft meer overhead en piekt een beetje.

Met meer dan een handvol rijen, crosstab() neemt snel de leiding.@Andreiy's query presteert ongeveer hetzelfde als mijn vereenvoudigde versie, de aggregatiefunctie in buitenste SELECT (min() , max() , sum() ) maakt geen meetbaar verschil (slechts twee rijen per groep).

Alles zoals verwacht, geen verrassingen, neem mijn setup en probeer het @home.



  1. SQL-query werkt in console, maar niet in python

  2. Rails in afwachting van migratie in rake db:test:prepare

  3. Lichtgewicht WordPress-installatie:WordPress installeren met SQLite

  4. Is dit genoeg voor een veilige site? (4 kleine functies)