PostgreSQL Crosstab Query

Czy ktoś wie jak tworzyć zapytania crosstab w PostgreSQL?
Na przykład mam następującą tabelę:

Section    Status    Count
A          Active    1
A          Inactive  2
B          Active    4
B          Inactive  5

Chciałbym, aby zapytanie zwróciło następujący crosstab:

Section    Active    Inactive
A          1         2
B          4         5
Czy to możliwe?
Author: Radek Postołowicz, 2010-06-09

6 answers

Zainstaluj dodatkowy moduł tablefunc raz NA bazę danych, która dostarcza funkcję crosstab(). Od Postgres 9.1 można używać CREATE EXTENSION za to:

CREATE EXTENSION IF NOT EXISTS tablefunc;

Ulepszony przypadek testowy

CREATE TABLE tbl (
   section   text
 , status    text
 , ct        integer  -- "count" is a reserved word in standard SQL
);

INSERT INTO tbl VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                    , ('C', 'Inactive', 7);  -- ('C', 'Active') is missing

Prosta forma - nie nadaje się do brakujących atrybutów

crosstab(text) z 1 parametr wejściowy:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- needs to be "ORDER BY 1,2" here
   ) AS ct ("Section" text, "Active" int, "Inactive" int);

Zwraca:

 Section | Active | Inactive
---------+--------+----------
 A       |      1 |        2
 B       |      4 |        5
 C       |      7 |           -- !!
  • nie ma potrzeby odlewania i zmiany nazwy.
  • zwróć uwagę na niepoprawny wynik dla C: wartość 7 jest wypełniona dla pierwszej kolumny. Czasami takie zachowanie jest pożądane, ale nie w tym przypadku użycia.
  • prosta forma jest również ograniczona do dokładnie trzech kolumn w dostarczonym zapytaniu wejściowym: nazwa wiersza, Kategoria, wartość . Nie ma miejsca na dodatkowe kolumny jak w alternatywie 2-parametrowej poniżej.

Bezpieczna forma

crosstab(text, text) z 2 parametry wejściowe:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- could also just be "ORDER BY 1" here

  , $$VALUES ('Active'::text), ('Inactive')$$
   ) AS ct ("Section" text, "Active" int, "Inactive" int);

Zwraca:

 Section | Active | Inactive
---------+--------+----------
 A       |      1 |        2
 B       |      4 |        5
 C       |        |        7  -- !!
  • Zwróć uwagę na poprawny wynik dla C.

  • drugi parametr może być dowolnym zapytaniem, które zwraca jeden wiersz na atrybut odpowiadający kolejności definicji kolumny na końcu. Często będziesz chciał odpytywać różne atrybuty z bazowej tabeli w następujący sposób:

    'SELECT DISTINCT attribute FROM tbl ORDER BY 1'
    
    To jest w instrukcji.

    Ponieważ musisz przeliterować wszystkie kolumny w liście definicji kolumn w każdym razie( z wyjątkiem wstępnie zdefiniowanych wariantów crosstabN()), zwykle bardziej wydajne jest dostarczenie krótkiej listy w wyrażeniu VALUES, Jak pokazano:

    $$VALUES ('Active'::text), ('Inactive')$$)
    

    Lub (nie w instrukcji):

    $$SELECT unnest('{Active,Inactive}'::text[])$$  -- short syntax for long lists
    
  • Używałem notowania dolara aby ułatwić cytowanie.

  • Możesz nawet wypisywać kolumny z różne typy danych Z crosstab(text, text) - o ile tekstowa reprezentacja wartości kolumna jest poprawnym wejściem dla typu docelowego. W ten sposób możesz mieć atrybuty innego rodzaju i wyjścia text, date, numeric itd. dla odpowiednich atrybutów. Na końcu rozdziału znajduje się przykład koducrosstab(text, text) w instrukcji .

db fiddle tutaj

Zaawansowane przykłady


\crosstabview w psql

Postgres 9.6 dodano tę meta-komendę do domyślnego interaktywnego terminala psql. Możesz uruchomić zapytanie, którego chcesz użyć jako pierwszego parametru crosstab() i podać je do \crosstabview (natychmiast lub w następnym kroku). Like:

db=> SELECT section, status, ct FROM tbl \crosstabview

Podobny wynik jak wyżej, ale jest to funkcja reprezentacji na wyłącznie po stronie klienta. Wiersze wejściowe są traktowane nieco inaczej, dlatego ORDER BY nie jest wymagane. Szczegóły dla \crosstabview w instrukcji. na dole tej strony jest więcej przykładów kodu.

Pokrewna odpowiedź na dba.SE w 2011 roku został wybrany na członka zarządu psql.]}



Poprzednio zaakceptowane odpowiedź jest nieaktualna.

  • Wariant funkcji {[30] } jest przestarzały. Drugi parametr integer jest ignorowany. Cytuję: current manual :

    crosstab(text sql, int N) ...

    Przestarzała wersja crosstab(text). Parametr {[34] } jest teraz ignorowany, ponieważ ilość kolumn wartości jest zawsze określana przez wywołujące zapytanie

  • Niepotrzebne Odlewanie i zmiana nazwy.

  • Nie powiedzie się, jeśli wiersz nie mają wszystkich atrybutów. Zobacz bezpieczny wariant z dwoma parametrami wejściowymi powyżej, aby poprawnie obsłużyć brakujące atrybuty.

  • ORDER BY jest wymagane w postaci jednoparametrowej crosstab(). Instrukcja:

    W praktyce zapytanie SQL powinno zawsze określać ORDER BY 1,2, aby zapewnić że wiersze wejściowe są prawidłowo uporządkowane

 353
Author: Erwin Brandstetter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-27 19:22:06

Możesz użyć funkcji crosstab() z dodatkowego modułu tablefunc - który musisz zainstalować raz na bazę danych. Od PostgreSQL 9.1 można używać CREATE EXTENSION za to:

CREATE EXTENSION tablefunc;

W Twoim przypadku wydaje mi się, że wyglądałoby to mniej więcej tak:

CREATE TABLE t (Section CHAR(1), Status VARCHAR(10), Count integer);

INSERT INTO t VALUES ('A', 'Active',   1);
INSERT INTO t VALUES ('A', 'Inactive', 2);
INSERT INTO t VALUES ('B', 'Active',   4);
INSERT INTO t VALUES ('B', 'Inactive', 5);

SELECT row_name AS Section,
       category_1::integer AS Active,
       category_2::integer AS Inactive
FROM crosstab('select section::text, status, count::text from t',2)
            AS ct (row_name text, category_1 text, category_2 text);
 32
Author: Jeremiah Peschka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-28 21:36:46
SELECT section,
       SUM(CASE status WHEN 'Active' THEN count ELSE 0 END) AS active, --here you pivot each status value as a separate column explicitly
       SUM(CASE status WHEN 'Inactive' THEN count ELSE 0 END) AS inactive --here you pivot each status  value as a separate column explicitly

FROM t
GROUP BY section
 29
Author: araqnid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-28 12:23:42

Rozwiązanie z agregacją JSON:

CREATE TEMP TABLE t (
  section   text
, status    text
, ct        integer  -- don't use "count" as column name.
);

INSERT INTO t VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                   , ('C', 'Inactive', 7); 


SELECT section,
       (obj ->> 'Active')::int AS active,
       (obj ->> 'Inactive')::int AS inactive
FROM (SELECT section, json_object_agg(status,ct) AS obj
      FROM t
      GROUP BY section
     )X
 11
Author: Milos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-24 12:31:41

Przykro mi, że to nie jest kompletne, ponieważ nie mogę go przetestować tutaj, ale może to cię podniesie we właściwym kierunku. Tłumaczę z czegoś, czego używam, co sprawia, że podobne zapytanie:

select mt.section, mt1.count as Active, mt2.count as Inactive
from mytable mt
left join (select section, count from mytable where status='Active')mt1
on mt.section = mt1.section
left join (select section, count from mytable where status='Inactive')mt2
on mt.section = mt2.section
group by mt.section,
         mt1.count,
         mt2.count
order by mt.section asc;

Kod, z którego pracuję to:

select m.typeID, m1.highBid, m2.lowAsk, m1.highBid - m2.lowAsk as diff, 100*(m1.highBid - m2.lowAsk)/m2.lowAsk as diffPercent
from mktTrades m
   left join (select typeID,MAX(price) as highBid from mktTrades where bid=1 group by typeID)m1
   on m.typeID = m1.typeID
   left join (select typeID,MIN(price) as lowAsk  from mktTrades where bid=0 group by typeID)m2
   on m1.typeID = m2.typeID
group by m.typeID, 
         m1.highBid, 
         m2.lowAsk
order by diffPercent desc;

Który zwróci typeID, najwyższą cenę i najniższą cenę oraz różnicę między nimi (dodatnia różnica oznaczałaby, że coś można kupić za mniej niż można sprzedać).

 1
Author: LanceH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-09 02:34:56

Crosstab funkcja jest dostępna pod rozszerzeniem tablefunc. Będziesz musiał utworzyć to rozszerzenie jeden raz dla bazy danych.

CREATE EXTENSION tablefunc;

możesz użyć poniższego kodu, aby utworzyć tabelę przestawną za pomocą zakładki krzyżowej:

create table test_Crosstab( section text,
<br/>status text,
<br/>count numeric)

<br/>insert into test_Crosstab values ( 'A','Active',1)
                <br/>,( 'A','Inactive',2)
                <br/>,( 'B','Active',4)
                <br/>,( 'B','Inactive',5)

select * from crosstab(
<br/>'select section
    <br/>,status
    <br/>,count
    <br/>from test_crosstab'
    <br/>)as ctab ("Section" text,"Active" numeric,"Inactive" numeric)
 -2
Author: Lekshmi Kurup,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-12 12:00:18