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

Refactoring di una funzione PL/pgSQL per restituire l'output di varie query SELECT

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 (alias float8 )

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