Pokud jste nenainstalovali přídavný modul tablefunc , spusťte tento příkaz jednou na databázi:
CREATE EXTENSION tablefunc;
Odpověď na otázku
Velmi základní řešení křížové tabulky pro váš případ:
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?
Zvláštní obtížnost zde je, že neexistuje žádná kategorie (cat
) v základní tabulce. Pro základní 1parametrový formulář můžeme pouze poskytnout fiktivní sloupec s fiktivní hodnotou sloužící jako kategorie. Hodnota je stejně ignorována.
Toto je jeden z vzácných případů kde druhý parametr pro crosstab()
funkce není potřeba , protože všechny NULL
hodnoty se podle definice tohoto problému objevují pouze v visících sloupcích vpravo. A pořadí lze určit podle hodnoty .
Pokud bychom měli skutečnou kategorii sloupec s názvy určujícími pořadí hodnot ve výsledku, potřebovali bychom dvouparametrový formulář z crosstab()
. Zde syntetizuji sloupec kategorie pomocí funkce okna row_number()
, na základ crosstab()
dne:
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?
Zbytek je do značné míry zaběhnutý. Další vysvětlení a odkazy naleznete v těchto úzce souvisejících odpovědích.
Základy:
Pokud neznáte crosstab()
, přečtěte si nejprve toto funkce!
- PostgreSQL Crosstab Query
Pokročilé:
- Otočte se do více sloupců pomocí Tablefunc
- Sloučit tabulku a protokol změn do zobrazení v PostgreSQL
Správné nastavení testu
Pro začátek byste měli poskytnout testovací případ:
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');
Dynamická kontingenční tabulka?
Nepříliš dynamické , přesto, jak poznamenal @Clodoaldo. Typy dynamické návratnosti je obtížné dosáhnout pomocí plpgsql. Ale existují způsoby, jak to obejít – s určitými omezeními .
Abych tedy zbytek dále nekomplikoval, demonstruji najednodušší testovací případ:
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);
Volejte:
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);
Vrátí:
row_name | val1 | val2 | val3
----------+------+------+------
A | 10 | 20 |
B | 3 | 4 |
C | 5 | |
D | 6 | 7 | 8
Vestavěná funkce tablefunc
modul
Modul tablefunc poskytuje jednoduchou infrastrukturu pro generické crosstab()
volání bez poskytnutí seznamu definic sloupců. Řada funkcí napsaných v C
(obvykle velmi rychle):
crosstabN()
crosstab1()
- crosstab4()
jsou předem definovány. Jeden malý bod:vyžadují a vracejí veškerý text
. Potřebujeme tedy přenést naše integer
hodnoty. Ale zjednodušuje to volání:
SELECT * FROM crosstab4('SELECT row_name, attrib, val::text -- cast!
FROM tbl ORDER BY 1,2')
Výsledek:
row_name | category_1 | category_2 | category_3 | category_4
----------+------------+------------+------------+------------
A | 10 | 20 | |
B | 3 | 4 | |
C | 5 | | |
D | 6 | 7 | 8 |
Vlastní crosstab()
funkce
Pro více sloupců nebo jiné datové typy , vytváříme vlastní složený typ a funkce (jednou).
Typ:
CREATE TYPE tablefunc_crosstab_int_5 AS (
row_name text, val1 int, val2 int, val3 int, val4 int, val5 int);
Funkce:
CREATE OR REPLACE FUNCTION crosstab_int_5(text)
RETURNS SETOF tablefunc_crosstab_int_5
AS '$libdir/tablefunc', 'crosstab' LANGUAGE c STABLE STRICT;
Volejte:
SELECT * FROM crosstab_int_5('SELECT row_name, attrib, val -- no cast!
FROM tbl ORDER BY 1,2');
Výsledek:
row_name | val1 | val2 | val3 | val4 | val5
----------+------+------+------+------+------
A | 10 | 20 | | |
B | 3 | 4 | | |
C | 5 | | | |
D | 6 | 7 | 8 | |
Jedna polymorfní, dynamická funkce pro všechny
To jde nad rámec toho, co pokrývá tablefunc
modul.
Aby byl návratový typ dynamický, používám polymorfní typ s technikou podrobně popsanou v této související odpovědi:
- Refaktorujte funkci PL/pgSQL tak, aby vrátila výstup různých SELECT dotazů
1parametrová forma:
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;
Přetížení s touto variantou pro 2parametrový formulář:
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
:Pro každý uživatelsky definovaný složený typ je definován typ řádku, takže atributy (sloupce) jsou uvedeny v systémovém katalogu pg_attribute
. Rychlý pruh, jak to získat:vrhněte registrovaný typ (regtype
) na text
a odešlete tento text
do regclass
.
Vytvořte složené typy jednou:
Musíte definovat jednou každý návratový typ, který budete používat:
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);
...
Pro ad-hoc hovory můžete také vytvořit dočasnou tabulku se stejným (dočasným) efektem:
CREATE TEMP TABLE temp_xtype7 AS (
row_name text, x1 int, x2 int, x3 int, x4 int, x5 int, x6 int, x7 int);
Nebo použijte typ existující tabulky, pohledu nebo materializovaného pohledu, pokud je k dispozici.
Zavolejte
Použití výše uvedených typů řádků:
1parametrový formulář (žádné chybějící hodnoty):
SELECT * FROM crosstab_n(
'SELECT row_name, attrib, val FROM tbl ORDER BY 1,2'
, NULL::tablefunc_crosstab_int_3);
2parametrová forma (některé hodnoty mohou chybět):
SELECT * FROM crosstab_n(
'SELECT row_name, attrib, val FROM tbl ORDER BY 1'
, $$VALUES ('val1'), ('val2'), ('val3')$$
, NULL::tablefunc_crosstab_int_3);
Tato jedna funkce funguje pro všechny typy návratů, zatímco crosstabN()
framework poskytovaný tablefunc
modul potřebuje pro každý samostatnou funkci.
Pokud jste své typy pojmenovali v pořadí, jak je ukázáno výše, stačí nahradit tučné číslo. Chcete-li zjistit maximální počet kategorií v základní tabulce:
SELECT max(count(*)) OVER () FROM tbl -- returns 3
GROUP BY row_name
LIMIT 1;
Pokud chcete jednotlivé sloupce, je to zhruba tak dynamické . Pole jako předvedené @Clocoaldo nebo jednoduchá textová reprezentace nebo výsledek zabalený do typu dokumentu, jako je json
nebo hstore
může dynamicky fungovat pro libovolný počet kategorií.
Odmítnutí odpovědnosti:
Vždy je to potenciálně nebezpečné, když je vstup uživatele převeden na kód. Ujistěte se, že to nelze použít pro SQL injection. Nepřijímejte vstupy od nedůvěryhodných uživatelů (přímo).
Zavolejte pro původní otázku:
SELECT * FROM crosstab_n('SELECT bar, 1, feh FROM tbl_org ORDER BY 1,2'
, NULL::tablefunc_crosstab_int_3);