PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Alternativa dinamica al pivot con CASE e GROUP BY

Se non hai installato il modulo aggiuntivo tablefunc , esegui questo comando una volta per database:

CREATE EXTENSION tablefunc;

Rispondi alla domanda

Una soluzione a campi incrociati molto semplice per il tuo caso:

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?

La difficoltà speciale ecco che non esiste una categoria (cat ) nella tabella di base. Per il modulo a 1 parametro di base possiamo semplicemente fornire una colonna fittizia con un valore fittizio che funge da categoria. Il valore viene comunque ignorato.

Questo è uno dei casi rari dove il secondo parametro per la crosstab() la funzione non è necessaria , perché tutti NULL i valori compaiono solo nelle colonne penzolanti a destra per definizione di questo problema. E l'ordine può essere determinato dal valore .

Se avessimo una vera categoria colonna con i nomi che determinano l'ordine dei valori nel risultato, avremmo bisogno del modulo a 2 parametri di crosstab() . Qui sintetizzo una colonna di categoria con l'aiuto della funzione della finestra row_number() , per basare crosstab() su:

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?

Il resto è praticamente banale. Trova ulteriori spiegazioni e collegamenti in queste risposte strettamente correlate.

Nozioni di base:
Leggi prima questo se non hai familiarità con il crosstab() funzione!

  • Query a campi incrociati PostgreSQL

Avanzate:

  • Pivota su più colonne utilizzando Tablefunc
  • Unisci una tabella e un log delle modifiche in una vista in PostgreSQL

Impostazione del test corretta

Ecco come dovresti fornire un test case per cominciare:

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

Tabella incrociata dinamica?

Non molto dinamico , ancora, come ha commentato @Clodoaldo. I tipi di ritorno dinamici sono difficili da ottenere con plpgsql. Ma ci ci sono modi per aggirarlo - con alcune limitazioni .

Quindi, per non complicare ulteriormente il resto, dimostro con un più semplice caso di prova:

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

Chiama:

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

Resi:

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

Funzione integrata di tablefunc modulo

Il modulo tablefunc fornisce una semplice infrastruttura per crosstab() generico chiamate senza fornire un elenco di definizioni di colonna. Un certo numero di funzioni scritte in C (solitamente molto veloce):

crosstabN()

crosstab1() - crosstab4() sono predefiniti. Un punto minore:richiedono e restituiscono tutto il text . Quindi dobbiamo eseguire il cast del nostro integer valori. Ma semplifica la chiamata:

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

Risultato:

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

Custom crosstab() funzione

Per altre colonne o altri tipi di dati , creiamo il nostro tipo composito e funzione (una volta).
Digita:

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

Funzione:

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

Chiama:

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

Risultato:

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

Uno funzione polimorfica e dinamica per tutti

Questo va oltre ciò che è coperto da tablefunc modulo.
Per rendere dinamico il tipo restituito, utilizzo un tipo polimorfico con una tecnica dettagliata in questa risposta correlata:

  • Refactoring di una funzione PL/pgSQL per restituire l'output di varie query SELECT

Modulo a 1 parametro:

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;

Sovraccarico con questa variante per il modulo a 2 parametri:

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 :Esiste un tipo di riga definito per ogni tipo composto definito dall'utente, in modo che gli attributi (colonne) siano elencati nel catalogo di sistema pg_attribute . La corsia preferenziale per ottenerlo:lancia il tipo registrato (regtype ) in text e lancia questo text a regclass .

Crea tipi compositi una volta:

Devi definire una volta ogni tipo di reso che intendi utilizzare:

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

...

Per le chiamate ad hoc, puoi anche creare un tavolo temporaneo allo stesso effetto (temporaneo):

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

Oppure usa il tipo di una tabella esistente, vista o vista materializzata se disponibile.

Chiama

Utilizzo dei tipi di riga sopra:

Modulo a 1 parametro (nessun valore mancante):

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

Modulo a 2 parametri (possono mancare alcuni valori):

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

Questa una funzione funziona per tutti i tipi restituiti, mentre il crosstabN() framework fornito da tablefunc modulo ha bisogno di una funzione separata per ciascuno.
Se hai nominato i tuoi tipi in sequenza come mostrato sopra, devi solo sostituire il numero in grassetto. Per trovare il numero massimo di categorie nella tabella di base:

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

È il massimo della dinamica se vuoi colonne singole . Array come dimostrato da @Clocoaldo o una semplice rappresentazione di testo o il risultato racchiuso in un tipo di documento come json o hstore può funzionare per qualsiasi numero di categorie in modo dinamico.

Disclaimer:
È sempre potenzialmente pericoloso quando l'input dell'utente viene convertito in codice. Assicurati che non possa essere utilizzato per SQL injection. Non accettare input da utenti non attendibili (direttamente).

Chiama per domanda originale:

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