复制具有动态列名的记录

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
  • The manual about format().

  • Information schema vs. system catalogs.