Alternativa più semplice alla tua risposta pubblicata. Dovrebbe funzionare molto meglio.
Questa funzione recupera una riga da una determinata tabella (in_table_name
) e il valore della chiave primaria (in_row_pk
), e la inserisce come nuova riga nella stessa tabella, con alcuni valori sostituiti (in_override_values
). Viene restituito il nuovo valore della chiave primaria per impostazione predefinita (pk_new
).
CREATE OR REPLACE FUNCTION f_clone_row(in_table_name regclass
, in_row_pk int
, in_override_values hstore
, OUT pk_new int) AS
$func$
DECLARE
_pk text; -- name of PK column
_cols text; -- list of names of other columns
BEGIN
-- Get name of PK column
SELECT INTO _pk a.attname
FROM pg_catalog.pg_index i
JOIN pg_catalog.pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = i.indkey[0] -- 1 PK col!
WHERE i.indrelid = 't'::regclass
AND i.indisprimary;
-- Get list of columns excluding PK column
_cols := array_to_string(ARRAY(
SELECT quote_ident(attname)
FROM pg_catalog.pg_attribute
WHERE attrelid = in_table_name -- regclass used as OID
AND attnum > 0 -- exclude system columns
AND attisdropped = FALSE -- exclude dropped columns
AND attname <> _pk -- exclude PK column
), ',');
-- INSERT cloned row with override values, returning new PK
EXECUTE format('
INSERT INTO %1$I (%2$s)
SELECT %2$s
FROM (SELECT (t #= $1).* FROM %1$I t WHERE %3$I = $2) x
RETURNING %3$I'
, in_table_name, _cols, _pk)
USING in_override_values, in_row_pk -- use override values directly
INTO pk_new; -- return new pk directly
END
$func$ LANGUAGE plpgsql;
Chiama:
SELECT f_clone_row('t', 1, '"col1"=>"foo_new","col2"=>"bar_new"'::hstore);
-
Usa
regclass
come tipo di parametro di input, quindi è possibile utilizzare solo nomi di tabella validi all'inizio e l'iniezione SQL è esclusa. La funzione fallisce anche prima e con maggiore grazia se dovessi fornire un nome di tabella illegale. -
Usa un
OUT
parametro (pk_new
) per semplificare la sintassi. -
Non è necessario calcolare manualmente il valore successivo per la chiave primaria. Viene inserito automaticamente e restituito a posteriori. Questo non è solo più semplice e veloce, ma eviti anche numeri di sequenza sprecati o fuori ordine.
-
Usa
format()
per semplificare l'assemblaggio della stringa di query dinamica e renderla meno soggetta a errori. Nota come utilizzo i parametri posizionali rispettivamente per identificatori e stringhe. -
Mi baso sulla tua ipotesi implicita che le tabelle consentite hanno una colonna di chiave primaria singola di tipo intero con una colonna predefinita . Tipicamente
serial
colonne. -
L'elemento chiave della funzione è il
INSERT
finale :- Unisci i valori di sostituzione con la riga esistente utilizzando
#=
operatore in una sottoselezione e scomponi immediatamente la riga risultante. - Quindi puoi selezionare solo le colonne pertinenti nel
SELECT
principale . - Lascia che Postgres assegni il valore predefinito per il PK e lo restituisca con il
RETURNING
clausola. - Scrivi il valore restituito in
OUT
parametro direttamente. - Tutto fatto con un unico comando SQL, generalmente più veloce.
- Unisci i valori di sostituzione con la riga esistente utilizzando