复制具有动态列名的记录
Copy records with dynamic column names
我在 PostgreSQL 9.3 中有两个包含不同列的表:
CREATE TABLE person1(
NAME TEXT NOT NULL,
AGE INT NOT NULL
);
CREATE TABLE person2(
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL
);
INSERT INTO person2 (Name, Age, ADDRESS, SALARY)
VALUES ('Piotr', 20, 'London', 80);
我想将记录从person2
复制到person1
,但是列名可以在程序中改变,所以我想select在程序中联合列名。所以我创建了一个包含列名交集的数组。接下来我使用函数:insert into .... select
,但是当我按名称将数组变量传递给函数时出现错误。像这样:
select column_name into name1 from information_schema.columns where table_name = 'person1';
select column_name into name2 from information_schema.columns where table_name = 'person2';
select * into cols from ( select * from name1 intersect select * from name2) as tmp;
-- Create array with name of columns
select array (select column_name::text from cols) into cols2;
CREATE OR REPLACE FUNCTION f_insert_these_columns(VARIADIC _cols text[])
RETURNS void AS
$func$
BEGIN
EXECUTE (
SELECT 'INSERT INTO person1 SELECT '
|| string_agg(quote_ident(col), ', ')
|| ' FROM person2'
FROM unnest(_cols) col
);
END
$func$ LANGUAGE plpgsql;
select * from cols2;
array
------------
{name,age}
(1 row)
SELECT f_insert_these_columns(VARIADIC cols2);
ERROR: column "cols2" does not exist
这是怎么回事?
您似乎假设 SQL 中的 SELECT INTO
会分配一个 变量 。但事实并非如此。
它创建了一个新的 table 并且不鼓励在 Postgres 中使用它。请改用高级 CREATE TABLE AS
。尤其是,因为plpgsql里面的SELECT INTO
的意思是不同的:
- Combine two tables into a new one so that select rows from the other one are ignored
关于 SQL 个变量:
- User defined variables in PostgreSQL
因此你不能像这样调用函数:
<strike>SELECT f_insert_these_columns(VARIADIC cols2);</strike>
这可行:
SELECT f_insert_these_columns(VARIADIC (TABLE cols2 LIMIT 1));
或清洁工:
SELECT f_insert_these_columns(VARIADIC array) -- "array" being the unfortunate column name
FROM cols2
LIMIT 1;
关于短 TABLE
语法:
更好的解决方案
要在两个 table 之间复制具有相同名称的列的所有行:
CREATE OR REPLACE FUNCTION f_copy_rows_with_shared_cols(
IN _tbl1 regclass
, IN _tbl2 regclass
, OUT rows int
, OUT columns text)
LANGUAGE plpgsql AS
$func$
BEGIN
SELECT INTO columns -- proper use of SELECT INTO!
string_agg(quote_ident(attname), ', ')
FROM (
SELECT attname
FROM pg_attribute
WHERE attrelid IN (_tbl1, _tbl2)
AND NOT attisdropped -- no dropped (dead) columns
AND attnum > 0 -- no system columns
GROUP BY 1
HAVING count(*) = 2
) sub;
EXECUTE format('INSERT INTO %1$s(%2$s) SELECT %2$s FROM %3$s'
, _tbl1, columns, _tbl2);
GET DIAGNOSTICS rows = ROW_COUNT; -- return number of rows copied
END
$func$;
致电:
SELECT * FROM f_copy_rows_with_shared_cols('public.person2', 'public.person1');
结果:
rows | columns
-----+---------
3 | name, age
要点
请注意在 plpgsql 中正确使用 SELECT INTO
进行赋值。
注意数据类型regclass
的使用。这允许使用模式限定的 table 名称(可选)并防御 SQL 注入尝试:
Table name as a PostgreSQL function parameter
关于GET DIAGNOSTICS
:
- Count rows affected by DELETE
关于OUT
参数:
- Returning from a function with OUT parameter
-
-
我在 PostgreSQL 9.3 中有两个包含不同列的表:
CREATE TABLE person1(
NAME TEXT NOT NULL,
AGE INT NOT NULL
);
CREATE TABLE person2(
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL
);
INSERT INTO person2 (Name, Age, ADDRESS, SALARY)
VALUES ('Piotr', 20, 'London', 80);
我想将记录从person2
复制到person1
,但是列名可以在程序中改变,所以我想select在程序中联合列名。所以我创建了一个包含列名交集的数组。接下来我使用函数:insert into .... select
,但是当我按名称将数组变量传递给函数时出现错误。像这样:
select column_name into name1 from information_schema.columns where table_name = 'person1';
select column_name into name2 from information_schema.columns where table_name = 'person2';
select * into cols from ( select * from name1 intersect select * from name2) as tmp;
-- Create array with name of columns
select array (select column_name::text from cols) into cols2;
CREATE OR REPLACE FUNCTION f_insert_these_columns(VARIADIC _cols text[])
RETURNS void AS
$func$
BEGIN
EXECUTE (
SELECT 'INSERT INTO person1 SELECT '
|| string_agg(quote_ident(col), ', ')
|| ' FROM person2'
FROM unnest(_cols) col
);
END
$func$ LANGUAGE plpgsql;
select * from cols2;
array
------------
{name,age}
(1 row)
SELECT f_insert_these_columns(VARIADIC cols2);
ERROR: column "cols2" does not exist
这是怎么回事?
您似乎假设 SQL 中的 SELECT INTO
会分配一个 变量 。但事实并非如此。
它创建了一个新的 table 并且不鼓励在 Postgres 中使用它。请改用高级 CREATE TABLE AS
。尤其是,因为plpgsql里面的SELECT INTO
的意思是不同的:
- Combine two tables into a new one so that select rows from the other one are ignored
关于 SQL 个变量:
- User defined variables in PostgreSQL
因此你不能像这样调用函数:
<strike>SELECT f_insert_these_columns(VARIADIC cols2);</strike>
这可行:
SELECT f_insert_these_columns(VARIADIC (TABLE cols2 LIMIT 1));
或清洁工:
SELECT f_insert_these_columns(VARIADIC array) -- "array" being the unfortunate column name
FROM cols2
LIMIT 1;
关于短 TABLE
语法:
更好的解决方案
要在两个 table 之间复制具有相同名称的列的所有行:
CREATE OR REPLACE FUNCTION f_copy_rows_with_shared_cols(
IN _tbl1 regclass
, IN _tbl2 regclass
, OUT rows int
, OUT columns text)
LANGUAGE plpgsql AS
$func$
BEGIN
SELECT INTO columns -- proper use of SELECT INTO!
string_agg(quote_ident(attname), ', ')
FROM (
SELECT attname
FROM pg_attribute
WHERE attrelid IN (_tbl1, _tbl2)
AND NOT attisdropped -- no dropped (dead) columns
AND attnum > 0 -- no system columns
GROUP BY 1
HAVING count(*) = 2
) sub;
EXECUTE format('INSERT INTO %1$s(%2$s) SELECT %2$s FROM %3$s'
, _tbl1, columns, _tbl2);
GET DIAGNOSTICS rows = ROW_COUNT; -- return number of rows copied
END
$func$;
致电:
SELECT * FROM f_copy_rows_with_shared_cols('public.person2', 'public.person1');
结果:
rows | columns
-----+---------
3 | name, age
要点
请注意在 plpgsql 中正确使用
SELECT INTO
进行赋值。注意数据类型
regclass
的使用。这允许使用模式限定的 table 名称(可选)并防御 SQL 注入尝试:Table name as a PostgreSQL function parameter
关于
GET DIAGNOSTICS
:- Count rows affected by DELETE
关于
OUT
参数:- Returning from a function with OUT parameter