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 supg_type
. Ma ho fatto un ulteriore passo avanti: -
Sostituisci
quote_literal(_val)
con inserimento diretto del valore tramite ilUSING
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 distring_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 chepg_type.typname
è sempre identico alpg_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 esplicitoRETURN
. Questa è solo una scorciatoia annotativa. A Pavel non piacerà, preferisce un esplicitoRETURN
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$;