sql >> Databáze >  >> RDS >> PostgreSQL

Transponovat řádky a sloupce (aka pivot) pouze s minimálním COUNT()?

CASE

Pokud je váš případ tak jednoduchý, jak je ukázáno, CASE příkaz udělá:

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;

Nezáleží na tom, zda použijete sum() , max() nebo min() jako agregační funkce ve vnějším dotazu. Všechny vedou v tomto případě ke stejné hodnotě.

SQL Fiddle

crosstab()

S více kategoriemi to bude jednodušší s crosstab() dotaz. To by také mělo být rychlejší pro větší stoly .

Musíte nainstalovat další modul tablefunc (jednou pro každou databázi). Od Postgres 9.1 je to tak jednoduché jako:

CREATE EXTENSION tablefunc;

Podrobnosti v této související odpovědi:

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

Žádné sqlfiddle pro tento, protože web nepovoluje další moduly.

Srovnávací

Abych ověřil svá tvrzení, provedl jsem rychlý benchmark s téměř reálnými daty v mé malé testovací databázi. PostgreSQL 9.1.6. Otestujte pomocí EXPLAIN ANALYZE , nejlepší z 10:

Testovací nastavení s 10020 řádky:

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;

Výsledky:

@bluefeet
Celková doba běhu:95,401 ms

@wildplasser (různé výsledky, zahrnuje řádky s count <= 3 )
Celková doba běhu:64,497 ms

@Andreiy (+ ORDER BY )
&@Erwin1 - CASE (obě mají přibližně stejný výkon)
Celková doba běhu:39,105 ms

@Erwin2 - crosstab()
Celková doba běhu:17,644 ms

Do značné míry proporcionální (ale irelevantní) výsledky pouze s 20 řádky. Pouze @wildplasser's CTE má větší režii a trochu stoupá.

S více než hrstkou řádků, crosstab() rychle přebírá vedení.@Andreiyho dotaz funguje přibližně stejně jako moje zjednodušená verze, agregační funkce ve vnějším SELECT (min() , max() , sum() ) nečiní žádný měřitelný rozdíl (jen dva řádky na skupinu).

Vše podle očekávání, žádná překvapení, vezměte si moje nastavení a vyzkoušejte to @home.



  1. Odstraňte duplicitní řádky mysql bez primárního klíče

  2. Zkrat Oracle CASE nefunguje ve skupině

  3. Node.js synchronně zacyklí nebo iteruje přes asynchronní příkazy

  4. Problém s ukládáním hodnot zeměpisné šířky a délky do databáze MySQL