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