Alternative plus simple à votre réponse publiée. Devrait être beaucoup plus performant.
Cette fonction récupère une ligne d'une table donnée (in_table_name
) et la valeur de la clé primaire (in_row_pk
), et l'insère en tant que nouvelle ligne dans la même table, avec certaines valeurs remplacées (in_override_values
). La nouvelle valeur de clé primaire par défaut est renvoyée (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;
Appel :
SELECT f_clone_row('t', 1, '"col1"=>"foo_new","col2"=>"bar_new"'::hstore);
-
Utilisez
regclass
comme type de paramètre d'entrée, seuls les noms de table valides peuvent donc être utilisés pour commencer et l'injection SQL est exclue. La fonction échoue également plus tôt et plus gracieusement si vous devez fournir un nom de table illégal. -
Utiliser un
OUT
paramètre (pk_new
) pour simplifier la syntaxe. -
Pas besoin de déterminer manuellement la valeur suivante pour la clé primaire. Il est inséré automatiquement et renvoyé après coup. C'est non seulement plus simple et plus rapide, mais vous évitez également les numéros de séquence inutiles ou dans le désordre.
-
Utilisez
format()
pour simplifier l'assemblage de la chaîne de requête dynamique et la rendre moins sujette aux erreurs. Notez comment j'utilise les paramètres positionnels pour les identificateurs et les chaînes respectivement. -
Je m'appuie sur votre hypothèse implicite que les tables autorisées ont une colonne de clé primaire unique de type entier avec une colonne par défaut . Typiquement
serial
colonnes. -
L'élément clé de la fonction est le dernier
INSERT
:- Fusionnez les valeurs de remplacement avec la ligne existante à l'aide de
#=
opérateur dans une sous-sélection et décompose immédiatement la ligne résultante. - Ensuite, vous pouvez sélectionner uniquement les colonnes pertinentes dans le
SELECT
principal . - Laissez Postgres attribuer la valeur par défaut pour le PK et récupérez-la avec le
RETURNING
clause. - Écrivez la valeur renvoyée dans le
OUT
paramètre directement. - Tout est fait en une seule commande SQL, ce qui est généralement le plus rapide.
- Fusionnez les valeurs de remplacement avec la ligne existante à l'aide de