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

Dynamisk alternativ til pivotering med CASE og GROUP BY

Hvis du ikke har installeret det ekstra modul tablefunc , kør denne kommando en gang per database:

CREATE EXTENSION tablefunc; 

Svar på spørgsmål

En meget grundlæggende krydstabuleringsløsning til dit tilfælde:

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); -- flere kolonner? 

Den særlige sværhedsgrad her er, at der ikke er nogen kategori (kat ) i basistabellen. Til den grundlæggende 1-parameter form vi kan bare levere en dummy-kolonne med en dummy-værdi, der fungerer som kategori. Værdien ignoreres alligevel.

Dette er et af de sjældne tilfælde hvor den anden parameter for crosstab() funktion er ikke nødvendig , fordi alle NULL værdier vises kun i hængende kolonner til højre ved definition af dette problem. Og rækkefølgen kan bestemmes af værdien .

Hvis vi havde en faktisk kategori kolonne med navne, der bestemmer rækkefølgen af ​​værdier i resultatet, har vi brug for 2-parameterformen af crosstab() . Her syntetiserer jeg en kategorikolonne ved hjælp af vinduesfunktionen row_number() , til at basere 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')$$ -- flere kolonner?) AS ct (bjælketekst, val1 int, val2 int, val3 int); -- flere kolonner? 

Resten er stort set løbe-af-møllen. Find flere forklaringer og links i disse nært beslægtede svar.

Grundlæggende:
Læs dette først, hvis du ikke er bekendt med crosstab() funktion!

  • PostgreSQL krydstabulatorforespørgsel

Avanceret:

  • Pivot på flere kolonner ved hjælp af Tablefunc
  • Flet en tabel og en ændringslog til en visning i PostgreSQL

Korrekt testopsætning

Sådan skal du give en testcase til at begynde 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 krydstabel?

Ikke særlig dynamisk , alligevel, som @Clodoaldo kommenterede. Dynamiske afkasttyper er svære at opnå med plpgsql. Men der er veje rundt - med nogle begrænsninger .

Så for ikke at komplicere resten yderligere, demonstrerer jeg med en enklere testcase:

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 til:

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

Returnerer:

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

Indbygget funktion i tablefunc modul

Tablefunc-modulet giver en simpel infrastruktur til generisk crosstab() opkald uden at angive en kolonnedefinitionsliste. En række funktioner skrevet i C (typisk meget hurtigt):

krydstabelN ()  

crosstab1() - crosstab4() er foruddefinerede. Et mindre punkt:de kræver og returnerer al tekst . Så vi skal kaste vores heltal værdier. Men det forenkler opkaldet:

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

Resultat:

rækkenavn | kategori_1 | kategori_2 | kategori_3 | kategori_4----------+-------------+--------------- +------------ A | 10 | 20 | | B | 3 | 4 | | C | 5 | | | D | 6 | 7 | 8 |

Tilpasset crosstab() funktion

For flere kolonner eller andre datatyper , opretter vi vores egen sammensatte type og funktion (en gang).
Typ:

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

Funktion:

OPRET ELLER ERSTAT FUNKTION crosstab_int_5(text) RETURNERER SETOF tablefunc_crosstab_int_5AS '$libdir/tablefunc', 'crosstab' SPROG c STABIL STRICT; 

Ring til:

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

Resultat:

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

En polymorf, dynamisk funktion for alle

Dette går ud over, hvad der er dækket af tablefunc modul.
For at gøre returtypen dynamisk bruger jeg en polymorf type med en teknik beskrevet i dette relaterede svar:

  • Refaktorer en PL/pgSQL-funktion for at returnere output fra forskellige SELECT-forespørgsler

1-parameter form:

OPRET ELLER ERSTAT FUNKTION crosstab_n(_qry text, _rowtype anyelement) RETURNERER 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 OG IKKE attisdropped); END$func$ LANGUAGE plpgsql; 

Overbelast med denne variant for 2-parameter-formen:

OPRET ELLER ERSTAT FUNKTION crosstab_n(_qry text, _cat_qry text, _rowtype anyelement) RETURNERER 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 , ', ' BESTIL EFTER attnum)) FRA pg_attribute WHERE attrelid =pg_typeof(_rowtype)::text::regclass OG attnum> 0 AND NOT attisdropped);END$func$ LANGUAGE plpgsql; 

pg_typeof(_rowtype)::text::regclass :Der er defineret en rækketype for hver brugerdefineret sammensat type, så attributter (kolonner) er angivet i systemkataloget pg_attribute . Den hurtige bane for at få det:cast den registrerede type (regtype ) til tekst og cast denne tekst til regclass .

Opret sammensatte typer én gang:

Du skal definere én gang for hver returtype, du skal bruge:

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

For ad hoc-opkald kan du også bare oprette en midlertidig tabel med samme (midlertidige) virkning:

OPRET TEMP TABLE temp_xtype7 AS ( rækkenavn tekst, x1 int, x2 int, x3 int, x4 int, x5 int, x6 int, x7 int); 

Eller brug typen af ​​en eksisterende tabel, visning eller materialiseret visning, hvis den er tilgængelig.

Ring

Brug af ovenstående rækketyper:

1-parameter form (ingen manglende værdier):

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

2-parameter form (nogle værdier kan mangle):

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

Denne en funktion virker for alle returtyper, mens krydstavlenN () framework leveret af tablefunc modul har brug for en separat funktion for hver.
Hvis du har navngivet dine typer i rækkefølge som vist ovenfor, skal du kun erstatte det fede tal. Sådan finder du det maksimale antal kategorier i basistabellen:

SELECT max(count(*)) OVER () FROM tbl -- returnerer 3GROUP BY row_nameLIMIT 1; 

Det er omtrent lige så dynamisk, som det bliver, hvis du vil have individuelle kolonner . Arrays som demonstreret af @Clocoaldo eller en simpel tekstrepræsentation eller resultatet pakket ind i en dokumenttype som json eller hstore kan arbejde for et vilkårligt antal kategorier dynamisk.

Ansvarsfraskrivelse:
Det er altid potentielt farligt, når brugerinput konverteres til kode. Sørg for, at dette ikke kan bruges til SQL-injektion. Accepter ikke input fra upålidelige brugere (direkte).

Ring for originalt spørgsmål:

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


  1. MySQL GROUP BY adfærd

  2. Sådan opretter du DMZ til EBS R12

  3. MySQL FLOOR() Funktion – Rund ned til nærmeste heltal

  4. Hvordan undgår man at SSIS FTP-opgave mislykkes, når der ikke er nogen filer at downloade?