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

Dynamiczna alternatywa dla pivota z CASE i GROUP BY

Jeśli nie zainstalowałeś dodatkowego modułu tablefunc , uruchom to polecenie raz na bazę danych:

CREATE EXTENSION tablefunc;

Odpowiedź na pytanie

Bardzo podstawowe rozwiązanie dla Twojej sprawy:

SELECT * FROM crosstab(
  'SELECT bar, 1 AS cat, feh
   FROM   tbl_org
   ORDER  BY bar, feh')
 AS ct (bar text, val1 int, val2 int, val3 int);  -- more columns?

specjalna trudność tutaj jest, że nie ma kategorii (cat ) w tabeli podstawowej. Dla podstawowej formy 1-parametrowej możemy po prostu podać fikcyjną kolumnę z fikcyjną wartością służącą jako kategoria. Wartość i tak jest ignorowana.

To jeden z rzadkich przypadków gdzie drugi parametr dla crosstab() funkcja nie jest potrzebna , ponieważ wszystkie NULL wartości pojawiają się tylko w nieaktualnych kolumnach po prawej stronie, zgodnie z definicją tego problemu. A kolejność można określić na podstawie wartości .

Gdybyśmy mieli rzeczywistą kategorię kolumna z nazwami określającymi kolejność wartości w wyniku, potrzebowalibyśmy formy 2-parametrowej z crosstab() . Tutaj syntetyzuję kolumnę kategorii za pomocą funkcji okna row_number() , aby bazować na crosstab() w dniu:

SELECT * FROM crosstab(
   $$
   SELECT bar, val, feh
   FROM  (
      SELECT *, 'val' || row_number() OVER (PARTITION BY bar ORDER BY feh) AS val
      FROM tbl_org
      ) x
   ORDER BY 1, 2
   $$
 , $$VALUES ('val1'), ('val2'), ('val3')$$         -- more columns?
) AS ct (bar text, val1 int, val2 int, val3 int);  -- more columns?

Reszta jest dość przeciętna. Znajdź więcej wyjaśnień i linków w tych ściśle powiązanych odpowiedziach.

Podstawy:
Przeczytaj to najpierw, jeśli nie znasz metody crosstab() funkcja!

  • Kwerendy PostgreSQL Crosstab

Zaawansowane:

  • Przestaw na wiele kolumn za pomocą funkcji Tablefunc
  • Połącz tabelę i dziennik zmian w widoku w PostgreSQL

Właściwa konfiguracja testu

W ten sposób powinieneś na początek dostarczyć przypadek testowy:

CREATE TEMP TABLE tbl_org (id int, feh int, bar text);
INSERT INTO tbl_org (id, feh, bar) VALUES
   (1, 10, 'A')
 , (2, 20, 'A')
 , (3,  3, 'B')
 , (4,  4, 'B')
 , (5,  5, 'C')
 , (6,  6, 'D')
 , (7,  7, 'D')
 , (8,  8, 'D');

Dynamiczna tabela przestawna?

Niezbyt dynamiczny , jednak, jak skomentował @Clodoaldo. Dynamiczne typy zwrotów są trudne do osiągnięcia za pomocą plpgsql. Ale są sposoby na obejście tego – z pewnymi ograniczeniami .

Aby nie komplikować dalej reszty, demonstruję za pomocą prostszego przypadek testowy:

CREATE TEMP TABLE tbl (row_name text, attrib text, val int);
INSERT INTO tbl (row_name, attrib, val) VALUES
   ('A', 'val1', 10)
 , ('A', 'val2', 20)
 , ('B', 'val1', 3)
 , ('B', 'val2', 4)
 , ('C', 'val1', 5)
 , ('D', 'val3', 8)
 , ('D', 'val1', 6)
 , ('D', 'val2', 7);

Zadzwoń:

SELECT * FROM crosstab('SELECT row_name, attrib, val FROM tbl ORDER BY 1,2')
AS ct (row_name text, val1 int, val2 int, val3 int);

Zwroty:

 row_name | val1 | val2 | val3
----------+------+------+------
 A        | 10   | 20   |
 B        |  3   |  4   |
 C        |  5   |      |
 D        |  6   |  7   |  8

Wbudowana funkcja tablefunc moduł

Moduł tablefunc zapewnia prostą infrastrukturę dla ogólnej crosstab() wywołania bez podania listy definicji kolumn. Szereg funkcji napisanych w C (zazwyczaj bardzo szybko):

crosstabN()

crosstab1() - crosstab4() są wstępnie zdefiniowane. Jedna drobna uwaga:wymagają i zwracają cały text . Więc musimy rzucić naszą integer wartości. Ale to upraszcza połączenie:

SELECT * FROM crosstab4('SELECT row_name, attrib, val::text  -- cast!
                         FROM tbl ORDER BY 1,2')

Wynik:

 row_name | category_1 | category_2 | category_3 | category_4
----------+------------+------------+------------+------------
 A        | 10         | 20         |            |
 B        | 3          | 4          |            |
 C        | 5          |            |            |
 D        | 6          | 7          | 8          |

Niestandardowy crosstab() funkcja

więcej kolumn lub inne typy danych , tworzymy własny typ złożony i funkcja (raz).
Wpisz:

CREATE TYPE tablefunc_crosstab_int_5 AS (
  row_name text, val1 int, val2 int, val3 int, val4 int, val5 int);

Funkcja:

CREATE OR REPLACE FUNCTION crosstab_int_5(text)
  RETURNS SETOF tablefunc_crosstab_int_5
AS '$libdir/tablefunc', 'crosstab' LANGUAGE c STABLE STRICT;

Zadzwoń:

SELECT * FROM crosstab_int_5('SELECT row_name, attrib, val   -- no cast!
                              FROM tbl ORDER BY 1,2');

Wynik:

 row_name | val1 | val2 | val3 | val4 | val5
----------+------+------+------+------+------
 A        |   10 |   20 |      |      |
 B        |    3 |    4 |      |      |
 C        |    5 |      |      |      |
 D        |    6 |    7 |    8 |      |

Jeden polimorficzna, dynamiczna funkcja dla wszystkich

Wykracza to poza to, co obejmuje tablefunc moduł.
Aby uczynić typ zwracany dynamicznym, używam typu polimorficznego z techniką opisaną w tej powiązanej odpowiedzi:

  • Refaktoryzuj funkcję PL/pgSQL, aby zwrócić dane wyjściowe różnych zapytań SELECT

Formularz 1-parametrowy:

CREATE OR REPLACE FUNCTION crosstab_n(_qry text, _rowtype anyelement)
  RETURNS SETOF anyelement AS
$func$
BEGIN
   RETURN QUERY EXECUTE 
   (SELECT format('SELECT * FROM crosstab(%L) t(%s)'
                , _qry
                , string_agg(quote_ident(attname) || ' ' || atttypid::regtype
                           , ', ' ORDER BY attnum))
    FROM   pg_attribute
    WHERE  attrelid = pg_typeof(_rowtype)::text::regclass
    AND    attnum > 0
    AND    NOT attisdropped);
END
$func$  LANGUAGE plpgsql;

Przeciążenie tym wariantem dla formularza 2-parametrowego:

CREATE OR REPLACE FUNCTION crosstab_n(_qry text, _cat_qry text, _rowtype anyelement)
  RETURNS SETOF anyelement AS
$func$
BEGIN
   RETURN QUERY EXECUTE 
   (SELECT format('SELECT * FROM crosstab(%L, %L) t(%s)'
                , _qry, _cat_qry
                , string_agg(quote_ident(attname) || ' ' || atttypid::regtype
                           , ', ' ORDER BY attnum))
    FROM   pg_attribute
    WHERE  attrelid = pg_typeof(_rowtype)::text::regclass
    AND    attnum > 0
    AND    NOT attisdropped);
END
$func$  LANGUAGE plpgsql;

pg_typeof(_rowtype)::text::regclass :Istnieje typ wiersza zdefiniowany dla każdego typu złożonego zdefiniowanego przez użytkownika, dzięki czemu atrybuty (kolumny) są wymienione w katalogu systemowym pg_attribute . Szybki tor, aby to uzyskać:rzut zarejestrowany typ (regtype ) na text i prześlij ten text do regclass .

Utwórz typy złożone raz:

Musisz zdefiniować raz każdy typ zwrotu, którego zamierzasz użyć:

CREATE TYPE tablefunc_crosstab_int_3 AS (
    row_name text, val1 int, val2 int, val3 int);

CREATE TYPE tablefunc_crosstab_int_4 AS (
    row_name text, val1 int, val2 int, val3 int, val4 int);

...

W przypadku połączeń ad hoc możesz też po prostu utworzyć tablicę tymczasową z takim samym (tymczasowym) skutkiem:

CREATE TEMP TABLE temp_xtype7 AS (
    row_name text, x1 int, x2 int, x3 int, x4 int, x5 int, x6 int, x7 int);

Lub użyj typu istniejącej tabeli, widoku lub widoku zmaterializowanego, jeśli jest dostępny.

Zadzwoń

Korzystanie z powyższych typów wierszy:

Formularz 1-parametrowy (bez braków danych):

SELECT * FROM crosstab_n(
   'SELECT row_name, attrib, val FROM tbl ORDER BY 1,2'
 , NULL::tablefunc_crosstab_int_3);

Formularz 2-parametrowy (może brakować niektórych wartości):

SELECT * FROM crosstab_n(
   'SELECT row_name, attrib, val FROM tbl ORDER BY 1'
 , $$VALUES ('val1'), ('val2'), ('val3')$$
 , NULL::tablefunc_crosstab_int_3);

Ta jedna funkcja działa dla wszystkich typów zwracanych, podczas gdy crosstabN() framework dostarczony przez tablefunc moduł wymaga osobnej funkcji dla każdego.
Jeśli nazwałeś swoje typy w kolejności, jak pokazano powyżej, wystarczy zastąpić pogrubioną liczbę. Aby znaleźć maksymalną liczbę kategorii w tabeli podstawowej:

SELECT max(count(*)) OVER () FROM tbl  -- returns 3
GROUP  BY row_name
LIMIT  1;

To prawie tak dynamiczne, jak to się dzieje, jeśli chcesz pojedynczych kolumn . Tablice, takie jak zademonstrowane przez @Clocoaldo lub prostą reprezentację tekstową lub wynik opakowany w typ dokumentu, taki jak json lub hstore może pracować dynamicznie dla dowolnej liczby kategorii.

Zastrzeżenie:
Konwertowanie danych wprowadzanych przez użytkownika na kod jest zawsze potencjalnie niebezpieczne. Upewnij się, że nie można tego użyć do wstrzykiwania SQL. Nie akceptuj danych wejściowych od niezaufanych użytkowników (bezpośrednio).

Zadzwoń do pierwotnego pytania:

SELECT * FROM crosstab_n('SELECT bar, 1, feh FROM tbl_org ORDER BY 1,2'
                       , NULL::tablefunc_crosstab_int_3);


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Zapętlaj wymiar tablicy w plpgsql

  2. Wyodrębnij rok z daty w PostgreSQL

  3. Skopiuj strukturę tabeli do nowej tabeli

  4. Skalowanie PostgreSQL dla dużych ilości danych

  5. Używanie docker-compose do tworzenia tabel w bazie postgresql