Om du inte har installerat tilläggsmodulen tablefunc , kör det här kommandot en gång per databas:
CREATE EXTENSION tablefunc;
Svar på fråga
En mycket grundläggande korstabelllösning för ditt fall:
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?
Den särskilda svårigheten här är att det inte finns någon kategori (cat
) i bastabellen. För det grundläggande 1-parametersformuläret vi kan bara tillhandahålla en dummykolumn med ett dummyvärde som fungerar som kategori. Värdet ignoreras ändå.
Detta är ett av de sällsynta fallen där den andra parametern för crosstab()
funktionen behövs inte , eftersom alla NULL
värden visas endast i hängande kolumner till höger som definition av detta problem. Och ordningen kan bestämmas av värdet .
Om vi hade en verklig kategori kolumn med namn som bestämmer ordningen på värden i resultatet, skulle vi behöva formen med två parametrar av crosstab()
. Här syntetiserar jag en kategorikolumn med hjälp av fönsterfunktionen row_number()
, till basen crosstab()
på:
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?
Resten är ganska mycket run-of-the-mill. Hitta fler förklaringar och länkar i dessa närbesläktade svar.
Grunderna:
Läs detta först om du inte är bekant med crosstab()
funktion!
- PostgreSQL Crosstab Query
Avancerat:
- Pivota på flera kolumner med Tablefunc
- Slå samman en tabell och en ändringslogg till en vy i PostgreSQL
Korrekt testinställning
Det är så du bör tillhandahålla ett testfall till att börja med:
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');
Dynamisk korstabell?
Inte särskilt dynamisk , ändå, som @Clodoaldo kommenterade. Dynamiska returtyper är svåra att uppnå med plpgsql. Men det finns sätt runt det - med vissa begränsningar .
Så för att inte komplicera resten ytterligare demonstrerar jag med en enklare testfall:
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);
Ring:
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);
Returnerar:
row_name | val1 | val2 | val3
----------+------+------+------
A | 10 | 20 |
B | 3 | 4 |
C | 5 | |
D | 6 | 7 | 8
Inbyggd funktion i tablefunc
modul
Tablefunc-modulen tillhandahåller en enkel infrastruktur för generisk crosstab()
anrop utan att tillhandahålla en kolumndefinitionslista. Ett antal funktioner skrivna i C
(vanligtvis väldigt snabbt):
crosstabN()
crosstab1()
- crosstab4()
är fördefinierade. En liten punkt:de kräver och returnerar all text
. Så vi måste casta vårt integer
värden. Men det förenklar samtalet:
SELECT * FROM crosstab4('SELECT row_name, attrib, val::text -- cast!
FROM tbl ORDER BY 1,2')
Resultat:
row_name | category_1 | category_2 | category_3 | category_4
----------+------------+------------+------------+------------
A | 10 | 20 | |
B | 3 | 4 | |
C | 5 | | |
D | 6 | 7 | 8 |
Anpassad crosstab()
funktion
För fler kolumner eller andra datatyper , skapar vi vår egen sammansatta typ och funktion (en gång).
Typ:
CREATE TYPE tablefunc_crosstab_int_5 AS (
row_name text, val1 int, val2 int, val3 int, val4 int, val5 int);
Funktion:
CREATE OR REPLACE FUNCTION crosstab_int_5(text)
RETURNS SETOF tablefunc_crosstab_int_5
AS '$libdir/tablefunc', 'crosstab' LANGUAGE c STABLE STRICT;
Ring:
SELECT * FROM crosstab_int_5('SELECT row_name, attrib, val -- no cast!
FROM tbl ORDER BY 1,2');
Resultat:
row_name | val1 | val2 | val3 | val4 | val5
----------+------+------+------+------+------
A | 10 | 20 | | |
B | 3 | 4 | | |
C | 5 | | | |
D | 6 | 7 | 8 | |
En polymorf, dynamisk funktion för alla
Detta går utöver vad som täcks av tablefunc
modul.
För att göra returtypen dynamisk använder jag en polymorf typ med en teknik som beskrivs i det här relaterade svaret:
- Refaktorera en PL/pgSQL-funktion för att returnera utdata från olika SELECT-frågor
1-parameter form:
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;
Överbelasta med denna variant för 2-parametersformen:
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
:Det finns en radtyp definierad för varje användardefinierad sammansatt typ, så att attribut (kolumner) listas i systemkatalogen pg_attribute
. Snabbbanan för att få det:cast den registrerade typen (regtype
) till text
och casta denna text
till regclass
.
Skapa sammansatta typer en gång:
Du måste definiera en gång för varje returtyp du ska använda:
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);
...
För ad hoc-samtal kan du också bara skapa en tillfällig tabell till samma (tillfälliga) effekt:
CREATE TEMP TABLE temp_xtype7 AS (
row_name text, x1 int, x2 int, x3 int, x4 int, x5 int, x6 int, x7 int);
Eller använd typen av en befintlig tabell, vy eller materialiserad vy om tillgänglig.
Ring
Använda ovanstående radtyper:
Form med 1 parameter (inga saknade värden):
SELECT * FROM crosstab_n(
'SELECT row_name, attrib, val FROM tbl ORDER BY 1,2'
, NULL::tablefunc_crosstab_int_3);
Form med 2 parametrar (vissa värden kan saknas):
SELECT * FROM crosstab_n(
'SELECT row_name, attrib, val FROM tbl ORDER BY 1'
, $$VALUES ('val1'), ('val2'), ('val3')$$
, NULL::tablefunc_crosstab_int_3);
Denna en funktion fungerar för alla returtyper, medan crosstabN()
ram som tillhandahålls av tablefunc
modul behöver en separat funktion för var och en.
Om du har namngett dina typer i sekvens som visas ovan behöver du bara byta ut det fetstilta numret. Så här hittar du det maximala antalet kategorier i bastabellen:
SELECT max(count(*)) OVER () FROM tbl -- returns 3
GROUP BY row_name
LIMIT 1;
Det är ungefär så dynamiskt som det här blir om du vill ha enskilda kolumner . Arrayer som demonstreras av @Clocoaldo eller en enkel textrepresentation eller resultatet insvept i en dokumenttyp som json
eller hstore
kan fungera dynamiskt för valfritt antal kategorier.
Ansvarsfriskrivning:
Det är alltid potentiellt farligt när användarinmatning konverteras till kod. Se till att detta inte kan användas för SQL-injektion. Acceptera inte input från opålitliga användare (direkt).
Ring för originalfråga:
SELECT * FROM crosstab_n('SELECT bar, 1, feh FROM tbl_org ORDER BY 1,2'
, NULL::tablefunc_crosstab_int_3);