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

Transponere rækker og kolonner (a.k.a. pivot) kun med et minimum COUNT()?

CASE

Hvis din sag er så enkel som vist, en CASE erklæring vil gøre:

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 er ligegyldigt, om du bruger sum() , max() eller min() som aggregeret funktion i den ydre forespørgsel. De resulterer alle i den samme værdi i dette tilfælde.

SQL Fiddle

crosstab()

Med flere kategorier vil det være nemmere med en crosstab() forespørgsel. Dette burde også være hurtigere for større borde .

Du skal installere det ekstra modul tablefunc (én gang pr. database). Siden Postgres 9.1 er det så simpelt som:

CREATE EXTENSION tablefunc;

Detaljer i dette relaterede 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 for denne, fordi webstedet ikke tillader yderligere moduler.

Benchmark

For at verificere mine påstande kørte jeg en hurtig benchmark med tæt på reelle data i min lille testdatabase. PostgreSQL 9.1.6. Test med EXPLAIN ANALYZE , bedst af 10:

Testopsætning med 10020 rækker:

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;

Resultater:

@bluefeet
Samlet køretid:95.401 ms

@wildplasser (forskellige resultater, inkluderer rækker med count <= 3 )
Samlet køretid:64.497 ms

@Andreiy (+ ORDER BY )
&@Erwin1 - CASE (begge yder omtrent det samme)
Samlet kørselstid:39.105 ms

@Erwin2 - crosstab()
Samlet køretid:17.644 ms

Stort set proportionale (men irrelevante) resultater med kun 20 rækker. Kun @wildplassers CTE har mere overhead og spikes lidt.

Med mere end en håndfuld rækker, crosstab() tager hurtigt føringen.@Andreiys forespørgsel udfører omtrent det samme som min forenklede version, aggregatfunktion i ydre SELECT (min() , max() , sum() ) gør ingen målbar forskel (kun to rækker pr. gruppe).

Alt som forventet, ingen overraskelser, tag mit setup og prøv det @home.



  1. Find forskel mellem leder og forrige i resultattabel

  2. Flet to tabeller og find overlappende datoer og huller

  3. Sådan registrerer du, om en værdi indeholder mindst et numerisk ciffer i MariaDB

  4. FEJL:ORA-00917:manglende komma ved brug af Hibernate med en eksisterende sekvens i oracle