PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Транспониране на редове и колони (известен още като завъртане) само с минимален COUNT()?

CASE

Ако вашият случай е толкова прост, колкото е показано, CASE ще свърши работа:

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;

Няма значение дали използвате sum() , max() или min() като агрегатна функция във външната заявка. Всички те водят до една и съща стойност в този случай.

SQL Fiddle

crosstab()

С повече категории ще бъде по-лесно с crosstab() заявка. Това също трябва да бъде по-бързо за по-големи маси .

Трябва да инсталирате допълнителния модул tablefunc (веднъж на база данни). След Postgres 9.1 това е толкова просто, колкото:

CREATE EXTENSION tablefunc;

Подробности в този свързан отговор:

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

Няма sqlfiddle за този, защото сайтът не позволява допълнителни модули.

Бенчмарк

За да проверя твърденията си, направих бърз бенчмарк с близки до реалните данни в моята малка тестова база данни. PostgreSQL 9.1.6. Тествайте с EXPLAIN ANALYZE , най-доброто от 10:

Тестова настройка с 10020 реда:

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;

Резултати:

@bluefeet
Общо време на изпълнение:95,401 ms

@wildplasser (различни резултати, включва редове с count <= 3 )
Общо време на работа:64,497 ms

@Andreiy (+ ORDER BY )
&@Erwin1 - CASE (и двете работят приблизително еднакво)
Общо време на работа:39,105 ms

@Erwin2 - crosstab()
Общо време на работа:17,644 ms

До голяма степен пропорционални (но неуместни) резултати само с 20 реда. Само CTE на @wildplasser има повече режийни разходи и леко скокове.

С повече от шепа редове, crosstab() бързо поема водеща роля. Заявката на @Andreiy изпълнява приблизително същото като моята опростена версия, агрегатна функция във външния SELECT (min() , max() , sum() ) не прави измерима разлика (само два реда на група).

Всичко според очакванията, без изненади, вземете моята настройка и опитайте @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. Как да предам таблица или редове към функция в Postgresql?

  2. ФАТАЛНА ГРЕШКА Файлът за заключване postmaster.pid вече съществува

  3. Надграждане от PostGIS 2.1:Грешка:опит за предефиниране на параметър postgis.backend

  4. Грешка при настройване на свойството за конфигурация на база данни за IDatabaseConnection (HSQLDB)

  5. Как да проверите дали клетка от тип integer array съдържа определена стойност в SQL