sql >> Databáze >  >> RDS >> PostgreSQL

Dynamická alternativa k pivotu s CASE a GROUP BY

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


  1. MySQL Seznam všech procedur

  2. STRING_SPLIT() v SQL Server 2016:Následná akce #2

  3. Záměna s Oracle CONNECT BY

  4. Importujte 'xml' do SQL Serveru