PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Transponować wiersze i kolumny (tzw. pivot) tylko z minimalną liczbą COUNT()?

CASE

Jeśli Twoja sprawa jest tak prosta, jak pokazano, CASE oświadczenie zrobi:

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;

Nie ma znaczenia, czy używasz sum() , max() lub min() jako funkcja agregująca w zapytaniu zewnętrznym. Wszystkie dają w tym przypadku tę samą wartość.

Skrzypce SQL

crosstab()

Przy większej liczbie kategorii łatwiej będzie dzięki crosstab() zapytanie. Powinno to być również szybsze przy większych stołach .

Musisz zainstalować dodatkowy moduł tablefunc (raz na bazę danych). Od Postgres 9.1 jest to tak proste, jak:

CREATE EXTENSION tablefunc;

Szczegóły w tej powiązanej odpowiedzi:

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

Brak sqlfiddle dla tego, ponieważ strona nie zezwala na dodatkowe moduły.

Wzorzec

Aby zweryfikować moje twierdzenia, przeprowadziłem szybki test porównawczy z danymi zbliżonymi do rzeczywistych w mojej małej testowej bazie danych. PostgreSQL 9.1.6. Przetestuj za pomocą EXPLAIN ANALYZE , najlepsze z 10:

Konfiguracja testowa z 10020 wierszami:

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;

Wyniki:

@bluefeet
Całkowity czas działania:95,401 ms

@wildplasser (różne wyniki, zawiera wiersze z count <= 3 )
Całkowity czas działania:64,497 ms

@Andreiy (+ ORDER BY )
&@Erwin1 - CASE (oba działają mniej więcej tak samo)
Całkowity czas działania:39,105 ms

@Erwin2 - crosstab()
Całkowity czas działania:17,644 ms

Wyniki w dużej mierze proporcjonalne (ale nieistotne) z zaledwie 20 wierszami. Tylko CTE @wildplassera ma więcej kosztów ogólnych i trochę wzrasta.

Mając więcej niż kilka wierszy, crosstab() szybko przejmuje prowadzenie.@ Zapytanie Andreiy działa mniej więcej tak samo jak moja uproszczona wersja, funkcja agregująca w zewnętrznym SELECT (min() , max() , sum() ) nie ma wymiernej różnicy (tylko dwa wiersze na grupę).

Wszystko zgodnie z oczekiwaniami, bez niespodzianek, weź moją konfigurację i wypróbuj ją w @home.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wygeneruj ERD dla istniejącej bazy danych

  2. Odwołanie do wiersza z innej tabeli (PostgreSQL)

  3. Apache Felix nie może uzyskać dostępu do Postgres JDBC

  4. Błąd bazy danych Postgres Nieprawidłowy nagłówek strony

  5. Najwcześniejszy znacznik czasu obsługiwany w PostgreSQL