sql >> Databasteknik >  >> RDS >> PostgreSQL

Transponera rader och kolumner (a.k.a. pivot) endast med ett minimum COUNT()?

CASE

Om ditt fall är så enkelt som visat, ett CASE uttalandet kommer att göra:

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;

Det spelar ingen roll om du använder sum() , max() eller min() som aggregerad funktion i den yttre frågan. De resulterar alla i samma värde i det här fallet.

SQL Fiddle

crosstab()

Med fler kategorier blir det enklare med en crosstab() fråga. Detta bör också vara snabbare för större bord .

Du måste installera tilläggsmodulen tablefunc (en gång per databas). Sedan Postgres 9.1 är det så enkelt som:

CREATE EXTENSION tablefunc;

Detaljer i detta relaterade svar:

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

Ingen sqlfiddle för denna eftersom webbplatsen inte tillåter ytterligare moduler.

Benchmark

För att verifiera mina påståenden körde jag en snabb benchmark med nästan verkliga data i min lilla testdatabas. PostgreSQL 9.1.6. Testa med EXPLAIN ANALYZE , bäst av 10:

Testinställning med 10020 rader:

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;

Resultat:

@bluefeet
Total körtid:95,401 ms

@wildplasser (olika resultat, inkluderar rader med count <= 3 )
Total körtid:64,497 ms

@Andreiy (+ ORDER BY )
&@Erwin1 - CASE (båda presterar ungefär likadant)
Total körtid:39,105 ms

@Erwin2 - crosstab()
Total körtid:17,644 ms

Till stor del proportionella (men irrelevanta) resultat med endast 20 rader. Bara @wildplassers CTE har mer overhead och spikar lite.

Med mer än en handfull rader, crosstab() tar snabbt ledningen.@Andreiys fråga utför ungefär samma som min förenklade version, aggregatfunktion i yttre SELECT (min() , max() , sum() ) gör ingen mätbar skillnad (bara två rader per grupp).

Allt som förväntat, inga överraskningar, ta min installation och prova @home.



  1. En stor sak:SQL Server 2016 Service Pack 1

  2. Kommandot python setup.py egg_info misslyckades med felkod 1 i /tmp/pip-install-fs0wmmw4/mysqlclient/

  3. Oracle 'Partition By' och 'Row_Number' nyckelord

  4. Mysql Skapa databas med specialtecken i namnet