sql >> Databasteknik >  >> RDS >> PostgreSQL

Kör en dynamisk korstabellsfråga

Det du ber om är omöjligt . SQL är ett strikt maskinskrivet språk. PostgreSQL-funktioner måste deklarera en returtyp (RETURNS .. ) vid tidpunkten för skapandet .

En begränsad väg runt detta är med polymorfa funktioner. Om du kan ange returtypen vid tillfället för funktionen ring . Men det framgår inte av din fråga.

  • Refaktorera en PL/pgSQL-funktion för att returnera utdata från olika SELECT-frågor

Du kan returnera ett helt dynamiskt resultat med anonyma poster. Men då måste du tillhandahålla en kolumndefinitionslista med varje samtal. Och hur vet du om de returnerade kolumnerna? Catch 22.

Det finns olika lösningar, beroende på vad du behöver eller kan arbeta med. Eftersom alla dina datakolumner verkar dela samma datatyp, föreslår jag att du returnerar en array :text[] . Eller så kan du returnera en dokumenttyp som hstore eller json . Relaterat:

  • Dynamiskt alternativ att pivotera med CASE och GROUP BY

  • Konvertera dynamiskt hstore-nycklar till kolumner för en okänd uppsättning nycklar

Men det kan vara enklare att bara använda två anrop:1:Låt Postgres bygga frågan. 2:Kör och hämta returnerade rader.

  • Välja flera max()-värden med en enda SQL-sats

Jag skulle inte använda funktionen från Eric Minikel som presenteras i din fråga överhuvudtaget . Det är inte säkert mot SQL-injektion med hjälp av uppsåtligt felaktiga identifierare. Använd format() att bygga frågesträngar om du inte kör en föråldrad version som är äldre än Postgres 9.1.

En kortare och renare implementering skulle kunna se ut så här:

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;

Samma funktionsanrop som din ursprungliga version. Funktionen crosstab() tillhandahålls av tilläggsmodulen tablefunc som måste installeras. Grunderna:

  • PostgreSQL Crosstab Query

Detta hanterar kolumn- och tabellnamn på ett säkert sätt. Notera användningen av objektidentifieringstyperna regclass och regtype . Fungerar även för schemakvalificerade namn.

  • Tabellnamn som en PostgreSQL-funktionsparameter

Det är dock inte helt säkert medan du skickar en sträng som ska köras som uttryck (_expr - cellc i din ursprungliga fråga). Denna typ av input är i sig osäkra mot SQL-injektion och bör aldrig exponeras för allmänheten.

  • SQL-injektion i Postgres-funktioner kontra förberedda frågor

Skannar tabellen bara en gång för båda listorna med kategorier och bör vara lite snabbare.

Kan fortfarande inte returnera helt dynamiska radtyper eftersom det absolut inte är möjligt.



  1. Var lagrar Android SQLites databasversion?

  2. Dynamisk kolumn i SELECT-sats postgres

  3. Vad är PL/SQL-lagrade procedurer i Oracle Database

  4. Uppdateringar av JSON-fältet kvarstår inte i DB