您发布的答案的更简单替代方案。应该表现得更好。
此函数从给定表 ( in_table_name
) 和主键值 ( in_row_pk
) 中检索一行,并将其作为新行插入到同一个表中,并替换一些值 ( in_override_values
)。返回默认的新主键值 ( 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;
称呼:
SELECT f_clone_row('t', 1, '"col1"=>"foo_new","col2"=>"bar_new"'::hstore);
SQL小提琴。
regclass
用作输入参数类型,所以只能使用有效的表名开头,排除SQL注入。如果您应该提供非法的表名,该函数也会更早且更优雅地失败。
使用OUT
参数 ( pk_new
) 来简化语法。
无需手动计算主键的下一个值。它会自动插入并在事后返回。这不仅更简单、更快捷,还可以避免浪费或乱序的序列号。
用于format()
简化动态查询字符串的组装并使其不易出错。请注意我如何分别将位置参数用于标识符和字符串。
我建立在您的隐含假设之上,即允许的表有一个整数类型的主键列和一个列 default。通常是serial
列。
函数的关键元素是 final INSERT
:
#=
使用子选择中的运算符将覆盖值与现有行合并,并立即分解结果行。
- 然后您可以只选择 main 中的相关列
SELECT
。
RETURNING
让 Postgres 为 PK 分配默认值并使用子句将其取回。
- 将返回值直接写入
OUT
参数。
- 所有这些都在一个 SQL 命令中完成,这通常是最快的。