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.