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

Udfør en dynamisk krydstabuleringsforespørgsel

Det, du beder om, er umuligt . SQL er et strengt maskinskrevet sprog. PostgreSQL-funktioner skal erklære en returtype (RETURNS .. ) på tidspunktet for oprettelse .

En begrænset vej rundt om dette er med polymorfe funktioner. Hvis du kan angive returtypen på tidspunktet for funktionen ring . Men det fremgår ikke af dit spørgsmål.

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

Du kan returnere et fuldstændig dynamisk resultat med anonyme registreringer. Men så er du forpligtet til at give en kolonnedefinitionsliste med hvert opkald. Og hvordan ved du om de returnerede kolonner? Catch 22.

Der er forskellige løsninger, alt efter hvad du har brug for eller kan arbejde med. Da alle dine datakolonner ser ud til at dele den samme datatype, foreslår jeg, at du returnerer en array :text[] . Eller du kan returnere en dokumenttype som hstore eller json . Relateret:

  • Dynamisk alternativ til pivotering med CASE og GROUP BY

  • Konverter dynamisk hstore-nøgler til kolonner for et ukendt sæt nøgler

Men det er måske nemmere blot at bruge to opkald:1:Lad Postgres bygge forespørgslen. 2:Udfør og hent returnerede rækker.

  • Valg af flere max()-værdier ved hjælp af en enkelt SQL-sætning

Jeg ville ikke bruge funktionen fra Eric Minikel som præsenteret i dit spørgsmål slet . Det er ikke sikkert mod SQL-injektion ved hjælp af ondsindet misdannede identifikatorer. Brug format() at bygge forespørgselsstrenge, medmindre du kører en forældet version, der er ældre end Postgres 9.1.

En kortere og renere implementering kunne se sådan ud:

CREATE OR REPLACE FUNCTION xtab(_tbl regclass, _row text, _cat text
                              , _expr text  -- still vulnerable to SQL injection!
                              , _type regtype)
  RETURNS text AS
$func$
DECLARE
   _cat_list text;
   _col_list text;
BEGIN

-- generate categories for xtab param and col definition list    
EXECUTE format(
 $$SELECT string_agg(quote_literal(x.cat), '), (')
        , string_agg(quote_ident  (x.cat), %L)
   FROM  (SELECT DISTINCT %I AS cat FROM %s ORDER BY 1) x$$
 , ' ' || _type || ', ', _cat, _tbl)
INTO  _cat_list, _col_list;

-- generate query string
RETURN format(
'SELECT * FROM crosstab(
   $q$SELECT %I, %I, %s
      FROM   %I
      GROUP  BY 1, 2  -- only works if the 3rd column is an aggregate expression
      ORDER  BY 1, 2$q$
 , $c$VALUES (%5$s)$c$
   ) ct(%1$I text, %6$s %7$s)'
, _row, _cat, _expr  -- expr must be an aggregate expression!
, _tbl, _cat_list, _col_list, _type
);

END
$func$ LANGUAGE plpgsql;

Samme funktionskald som din originale version. Funktionen crosstab() leveres af det ekstra modul tablefunc som skal installeres. Grundlæggende:

  • PostgreSQL krydstabulatorforespørgsel

Dette håndterer kolonne- og tabelnavne sikkert. Bemærk brugen af ​​objektidentifikatortyper regclass og regtype . Fungerer også for skema-kvalificerede navne.

  • Tabelnavn som en PostgreSQL-funktionsparameter

Det er dog ikke helt sikkert mens du sender en streng, der skal udføres som udtryk (_expr - cellc i din oprindelige forespørgsel). Denne form for input er i sagens natur usikre mod SQL-injektion og bør aldrig eksponeres for den brede offentlighed.

  • SQL-injektion i Postgres-funktioner kontra forberedte forespørgsler

Scanner kun tabellen én gang for begge lister over kategorier og burde være en smule hurtigere.

Kan stadig ikke returnere fuldstændig dynamiske rækketyper, da det strengt taget ikke er muligt.



  1. Indfyldning af displayvareværdi på forespørgsel i Oracle-formularer

  2. PostgreSQL slette alt indhold

  3. Halloween-problemet – del 2

  4. En eksplicit værdi for identitetskolonnen i tabellen kan kun angives, når der bruges en kolonneliste, og IDENTITY_INSERT er PÅ SQL Server