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

Come impostare il valore del campo della variabile composita utilizzando SQL dinamico

Più veloce con hstore

Da Postgres 9.0 , con il modulo aggiuntivo hstore installato nel tuo database c'è una soluzione molto semplice e veloce con il #= operatore che ...

sostituire[s] i campi in record con valori corrispondenti da hstore .

Per installare il modulo:

CREATE EXTENSION hstore;

Esempi:

SELECT my_record #= '"field"=>"value"'::hstore;  -- with string literal
SELECT my_record #= hstore(field, value);        -- with values

I valori devono essere trasmessi a text e ritorno, ovviamente.

Esempi di funzioni plpgsql con maggiori dettagli:

  • Ciclo infinito nella funzione di attivazione
  • Assegna a NUOVO tramite chiave in un trigger Postgres

Ora funziona con json / jsonb anche tu!

Esistono soluzioni simili con json (pag 9.3+) o jsonb (pag. 9.4+)

SELECT json_populate_record (my_record, json_build_object('key', 'new-value');

La funzionalità non era documentata, ma è ufficiale da Postgres 13. Il manuale:

Tuttavia, se la base non è NULL, i valori che contiene verranno utilizzati per le colonne non corrispondenti.

Quindi puoi prendere qualsiasi riga esistente e riempire campi arbitrari (sovrascrivendo ciò che contiene).

Principali vantaggi di json rispetto a hstore :

  • funziona con Postgres di serie, quindi non hai bisogno di un modulo aggiuntivo.
  • funziona anche per array nidificati e tipi compositi.

Piccolo svantaggio:un po' più lento.

Vedi la risposta aggiunta di @Geir per i dettagli.

Senza hstore e json

Se utilizzi una versione precedente o non riesci a installare il modulo aggiuntivo hstore o non posso presumere che sia installato, ecco una versione migliorata di ciò che ho pubblicato in precedenza. Ancora più lento di hstore operatore, però:

CREATE OR REPLACE FUNCTION f_setfield(INOUT _comp_val anyelement
                                          , _field text, _val text)
  RETURNS anyelement
  LANGUAGE plpgsql STABLE AS
$func$
BEGIN

EXECUTE 'SELECT ' || array_to_string(ARRAY(
      SELECT CASE WHEN attname = _field
                THEN '$2'
                ELSE '($1).' || quote_ident(attname)
             END AS fld
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = pg_typeof(_comp_val)::text::regclass
      AND    attnum > 0
      AND    attisdropped = FALSE
      ORDER  BY attnum
      ), ',')
USING  _comp_val, _val
INTO   _comp_val;

END
$func$;

Chiama:

CREATE TEMP TABLE t( a int, b text);  -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');

Note

  • Un cast esplicito del valore _val al tipo di dati di destinazione non è necessario, una stringa letterale nella query dinamica verrebbe automaticamente forzata, ovviando alla sottoquery su pg_type . Ma ho fatto un ulteriore passo avanti:

  • Sostituisci quote_literal(_val) con inserimento diretto del valore tramite il USING clausola. Salva una chiamata di funzione e due cast ed è comunque più sicuro. text è forzato automaticamente al tipo di destinazione nel moderno PostgreSQL. (Non è stato eseguito il test con versioni precedenti alla 9.1.)

  • array_to_string(ARRAY()) è più veloce di string_agg() .

  • Nessuna variabile necessaria, nessun DECLARE . Meno incarichi.

  • Nessuna sottoquery nell'SQL dinamico. ($1).field è più veloce.

  • pg_typeof(_comp_val)::text::regclass
    fa lo stesso di
    (SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
    per tipi compositi validi, solo più veloce.
    Questa ultima modifica è basata sul presupposto che pg_type.typname è sempre identico al pg_class.relname associato per i tipi compositi registrati e il doppio cast può sostituire la sottoquery. Ho eseguito questo test in un grande database per verificarlo ed è risultato vuoto come previsto:

    SELECT *
    FROM   pg_catalog.pg_type t
    JOIN   pg_namespace  n ON n.oid = t.typnamespace
    WHERE  t.typrelid > 0  -- exclude non-composite types
    AND    t.typrelid IS DISTINCT FROM
          (quote_ident(n.nspname ) || '.' || quote_ident(typname))::regclass
  • L'uso di un INOUT ovvia alla necessità di un esplicito RETURN . Questa è solo una scorciatoia annotativa. A Pavel non piacerà, preferisce un esplicito RETURN dichiarazione ...

Tutto messo insieme è due volte più veloce come la versione precedente.

Risposta originale (obsoleta):

Il risultato è una versione ~ 2,25 volte più veloce . Ma probabilmente non avrei potuto farlo senza basarmi sulla seconda versione di Pavel.

Inoltre, questa versione evita la maggior parte del casting al testo e viceversa facendo tutto all'interno di una singola query, quindi dovrebbe essere molto meno soggetto a errori.
Testato con PostgreSQL 9.0 e 9.1 .

CREATE FUNCTION f_setfield(_comp_val anyelement, _field text, _val text)
  RETURNS anyelement
  LANGUAGE plpgsql STABLE AS
$func$
DECLARE
   _list text;
BEGIN
_list := (
   SELECT string_agg(x.fld, ',')
   FROM  (
      SELECT CASE WHEN a.attname = $2
              THEN quote_literal($3) || '::'|| (SELECT quote_ident(typname)
                                                FROM   pg_catalog.pg_type
                                                WHERE  oid = a.atttypid)
              ELSE quote_ident(a.attname)
             END AS fld
      FROM   pg_catalog.pg_attribute a 
      WHERE  a.attrelid = (SELECT typrelid
                           FROM   pg_catalog.pg_type
                           WHERE  oid = pg_typeof($1)::oid)
      AND    a.attnum > 0
      AND    a.attisdropped = false
      ORDER  BY a.attnum
      ) x
   );

EXECUTE 'SELECT ' || _list || ' FROM  (SELECT $1.*) x'
USING  $1
INTO   $1;

RETURN $1;
END
$func$;