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

Usa l'output di testo da una funzione come nuova query

Il trucco con PREPARE non funziona, poiché non richiede una *stringa di testo* (un valore) come CREATE FUNCTION lo fa, ma una dichiarazione valida (codice).

Per convertire dati nel codice eseguibile devi usare SQL dinamico, ad esempio EXECUTE in una funzione plpgsql o DO dichiarazione. Funziona senza problemi fintanto che il tipo restituito non dipende dal risultato della prima funzione myresult() . Altrimenti sei tornato a prendere 22 come descritto nella mia risposta precedente:

  • Come eseguire un risultato stringa di una procedura memorizzata in postgres

La parte cruciale è dichiarare il tipo di reso (tipo di riga in questo caso) in qualche modo. Puoi creare una TABLE , TEMP TABLE o TYPE allo scopo. Oppure puoi usare una dichiarazione preparata o un recursor.

Soluzione con dichiarazione preparata

Sei stato molto vicino. Il pezzo mancante del puzzle è preparare la query generata con SQL dinamico .

Funzione per preparare la dichiarazione in modo dinamico

Crea questa funzione una volta . È una versione ottimizzata e sicura della tua funzione myresult() :

CREATE OR REPLACE FUNCTION f_prep_query (_tbl regclass, _prefix text)
  RETURNS void AS 
$func$
BEGIN
   IF EXISTS (SELECT 1 FROM pg_prepared_statements WHERE name = 'stmt_dyn') THEN
      DEALLOCATE stmt_dyn;
   END IF;                 -- you my or may not need this safety check 

   EXECUTE (
     SELECT 'PREPARE stmt_dyn AS SELECT '
         || string_agg(quote_ident(attname), ',' ORDER BY attname)
         || ' FROM ' || _tbl
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = _tbl
      AND    attname LIKE _prefix || '%'
      AND    attnum > 0
      AND    NOT attisdropped
     );
END
$func$  LANGUAGE plpgsql;

Uso regclass per il parametro nome tabella _tbl per renderlo univoco e sicuro contro SQLi. Dettagli:

  • Nome tabella come parametro di funzione PostgreSQL

Lo schema delle informazioni non include la colonna oid dei cataloghi di sistema, quindi sono passato a pg_catalog.pg_attribute invece di information_schema.columns . Anche quello è più veloce. Ci sono pro e contro per questo:

  • Come verificare se esiste una tabella in un determinato schema

Se un'istruzione preparata con il nome stmt_dyn già esistente, PREPARE solleverebbe un'eccezione. Se ciò è accettabile, rimuovere il segno di spunta dalla vista di sistema pg_prepared_statements e il seguente DEALLOCATE .
Algoritmi più sofisticati sono possibili per gestire più istruzioni preparate per sessione, o prendere il nome dell'istruzione preparata come parametro aggiuntivo, o anche usare un hash MD5 della stringa di query come nome, ma questo va oltre portata di questa domanda.

Tieni presente che PREPARE opera al di fuori dell'ambito delle transazioni , una volta PREPARE riesce, l'istruzione preparata esiste per la durata della sessione. Se la transazione di wrapping viene interrotta, PREPARE è inalterato. ROLLBACK non posso rimuovere le dichiarazioni preparate.

Esecuzione dinamica della query

Due query, ma solo una chiamare il server. E anche molto efficiente.

SELECT f_prep_query('tbl'::regclass, 'pre'::text);
EXECUTE stmt_dyn;

Più semplice e molto più efficiente per i casi d'uso più semplici rispetto alla creazione di una tabella temporanea o di un cursore e alla selezione / recupero da quello (che sarebbero altre opzioni).

SQL Fiddle.