Alternativa más simple a su respuesta publicada. Debería funcionar mucho mejor.
Esta función recupera una fila de una tabla dada (in_table_name
) y el valor de la clave principal (in_row_pk
), y lo inserta como una fila nueva en la misma tabla, con algunos valores reemplazados (in_override_values
). Se devuelve el nuevo valor de clave principal predeterminado (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;
Llamar:
SELECT f_clone_row('t', 1, '"col1"=>"foo_new","col2"=>"bar_new"'::hstore);
-
Use
regclass
como tipo de parámetro de entrada, por lo que solo se pueden usar nombres de tabla válidos para empezar y se descarta la inyección de SQL. La función también falla antes y con más gracia si debe proporcionar un nombre de tabla ilegal. -
Usa un
OUT
parámetro (pk_new
) para simplificar la sintaxis. -
No es necesario averiguar el siguiente valor de la clave principal de forma manual. Se inserta automáticamente y se devuelve después del hecho. Eso no solo es más simple y rápido, sino que también evita números de secuencia desperdiciados o desordenados.
-
Use
format()
para simplificar el ensamblaje de la cadena de consulta dinámica y hacerla menos propensa a errores. Tenga en cuenta cómo uso parámetros posicionales para identificadores y cadenas respectivamente. -
Me baso en su suposición implícita que las tablas permitidas tienen una columna de clave principal única de tipo entero con una columna predeterminada . Por lo general,
serial
columnas. -
El elemento clave de la función es el
INSERT
final :- Combina los valores de anulación con la fila existente usando
#=
operador en una subselección y descomponga la fila resultante inmediatamente. - Luego puede seleccionar solo columnas relevantes en el
SELECT
principal . - Deje que Postgres asigne el valor predeterminado para el PK y recupérelo con
RETURNING
cláusula. - Escriba el valor devuelto en
OUT
parámetro directamente. - Todo hecho en un solo comando SQL, que generalmente es el más rápido.
- Combina los valores de anulación con la fila existente usando