SQL dinamico e RETURN
digita
Vuoi eseguire SQL dinamico . In linea di massima, è semplice in plpgsql con l'aiuto di EXECUTE
. Non è necessario un cursore. In effetti, la maggior parte delle volte stai meglio senza cursori espliciti.
Il problema in cui ti imbatti:vuoi restituire record di tipo ancora non definito . Una funzione deve dichiarare il suo tipo restituito in RETURNS
clausola (o con OUT
o INOUT
parametri). Nel tuo caso dovresti ricorrere a record anonimi, perché numero , nomi e tipi delle colonne restituite variano. Come:
CREATE FUNCTION data_of(integer)
RETURNS SETOF record AS ...
Tuttavia, questo non è particolarmente utile. È necessario fornire un elenco di definizioni di colonna con ogni chiamata. Come:
SELECT * FROM data_of(17)
AS foo (colum_name1 integer
, colum_name2 text
, colum_name3 real);
Ma come faresti anche se non conosci le colonne in anticipo?
Potresti utilizzare tipi di dati di documenti meno strutturati come json
, jsonb
, hstore
o xml
. Vedi:
- Come memorizzare una tabella di dati nel database?
Ma, ai fini di questa domanda, supponiamo che tu voglia restituire il più possibile singole colonne, correttamente digitate e denominate.
Soluzione semplice con tipo di reso fisso
La colonna datahora
sembra essere un dato di fatto, presumo il tipo di dati timestamp
e che ci sono sempre altre due colonne con nome e tipo di dati diversi.
Nomi abbandoneremo a favore di nomi generici nel tipo restituito.
Tipi anche noi abbandoneremo e trasmetteremo tutto a text
da ogni il tipo di dati può essere trasmesso a text
.
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, col2 text, col3 text)
LANGUAGE plpgsql AS
$func$
DECLARE
_sensors text := 'col1::text, col2::text'; -- cast each col to text
_type text := 'foo';
BEGIN
RETURN QUERY EXECUTE '
SELECT datahora, ' || _sensors || '
FROM ' || quote_ident(_type) || '
WHERE id = $1
ORDER BY datahora'
USING _id;
END
$func$;
Le variabili _sensors
e _type
potrebbero invece essere parametri di input.
Nota la RETURNS TABLE
clausola.
Nota l'uso di RETURN QUERY EXECUTE
. Questo è uno dei modi più eleganti per restituire righe da una query dinamica.
Uso un nome per il parametro della funzione, solo per fare il USING
clausola di RETURN QUERY EXECUTE
meno confuso. $1
nella stringa SQL non fa riferimento al parametro della funzione ma al valore passato con il USING
clausola. (entrambi sono $1
nel rispettivo ambito in questo semplice esempio.)
Nota il valore di esempio per _sensors
:viene eseguito il cast di ogni colonna per digitare text
.
Questo tipo di codice è molto vulnerabile a SQL injection . Uso quote_ident()
per proteggersi da esso. Raggruppando insieme un paio di nomi di colonne nella variabile _sensors
impedisce l'uso di quote_ident()
(ed è in genere una cattiva idea!). Assicurati che nessun elemento negativo possa trovarsi lì in qualche altro modo, ad esempio eseguendo individualmente i nomi delle colonne tramite quote_ident()
invece. Un VARIADIC
mi viene in mente il parametro...
Più semplice da PostgreSQL 9.1
Con la versione 9.1 o successive puoi usare format()
per semplificare ulteriormente:
RETURN QUERY EXECUTE format('
SELECT datahora, %s -- identifier passed as unescaped string
FROM %I -- assuming the name is provided by user
WHERE id = $1
ORDER BY datahora'
,_sensors, _type)
USING _id;
Anche in questo caso, i nomi delle singole colonne potrebbero essere sottoposti a escape correttamente e sarebbe il modo più pulito.
Numero variabile di colonne che condividono lo stesso tipo
Dopo che la tua domanda è stata aggiornata, sembra che il tuo tipo di reso lo sia
- una variabile numero di colonne
- ma tutte colonne dello stesso tipo
double precision
(aliasfloat8
)
Usa un ARRAY
digitare in questo caso per annidare un numero variabile di valori. Inoltre, restituisco un array con i nomi delle colonne:
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, names text[], values float8[])
LANGUAGE plpgsql AS
$func$
DECLARE
_sensors text := 'col1, col2, col3'; -- plain list of column names
_type text := 'foo';
BEGIN
RETURN QUERY EXECUTE format('
SELECT datahora
, string_to_array($1) -- AS names
, ARRAY[%s] -- AS values
FROM %s
WHERE id = $2
ORDER BY datahora'
, _sensors, _type)
USING _sensors, _id;
END
$func$;
Vari tipi di tabelle complete
Per restituire effettivamente tutte le colonne di una tabella , esiste una soluzione semplice e potente che utilizza un tipo polimorfico :
CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
RETURNS SETOF anyelement
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE format('
SELECT *
FROM %s -- pg_typeof returns regtype, quoted automatically
WHERE id = $1
ORDER BY datahora'
, pg_typeof(_tbl_type))
USING _id;
END
$func$;
Chiama (importante!):
SELECT * FROM data_of(NULL::pcdmet, 17);
Sostituisci pcdmet
nella chiamata con qualsiasi altro nome di tabella.
Come funziona?
anyelement
è uno pseudo tipo di dati, un tipo polimorfico, un segnaposto per qualsiasi tipo di dati non array. Tutte le occorrenze di anyelement
nella funzione valutano lo stesso tipo fornito in fase di esecuzione. Fornendo un valore di un tipo definito come argomento alla funzione, definiamo implicitamente il tipo restituito.
PostgreSQL definisce automaticamente un tipo di riga (un tipo di dati composito) per ogni tabella creata, quindi esiste un tipo ben definito per ogni tabella. Ciò include tabelle temporanee, che è conveniente per l'uso ad hoc.
Qualsiasi tipo può essere NULL
. Consegna un NULL
value, cast al tipo di tabella:NULL::pcdmet
.
Ora la funzione restituisce un tipo di riga ben definito e possiamo usare SELECT * FROM data_of()
per scomporre la riga e ottenere singole colonne.
pg_typeof(_tbl_type)
restituisce il nome della tabella come identificatore di oggetto tipo regtype
. Quando viene convertito automaticamente in text
, gli identificatori sono automaticamente tra virgolette doppie e qualificati per lo schema se necessario, difendersi automaticamente da SQL injection. Questo può anche gestire nomi di tabelle qualificati per schema dove quote_ident()
fallirebbe. Vedi:
- Nome tabella come parametro di funzione PostgreSQL