如何从动态查询中插入具有多列的table?

How to INSERT INTO table having multiple column from dynamic query?

类似于 的一对一映射,我需要一个针对源和目标中多列的解决方案。
仍在使用 Postgres 9.4.4,查询和模式已修改如下:

假设我有这两个 tables Table1Table2:

Create table Table1(col1 int, col2 varchar(100), col3 varchar(100));
Create table Table2(col1 int, col2 varchar(100), col3 varchar(100));

还有一个table Table3存储了一个从Table1迁移数据到Table2的公式:

CREATE TABLE Table3 (     
  tbl_src character varying(200),
  col_src character varying(500),
  tbl_des character varying(200),
  col_des character varying(100),
  condition character varying(500)
);

Insert into Table3(tbl_src, col_src, tbl_des, col_des, condition)
VALUES ('Table1','col1','Table2','col1', 'WHERE col1>=1')
     , ('Table1','col2','Table2','col2', NULL)
     , ('Table1','col3','Table2','col3', NULL);

如何在动态查询中编译此公式并插入到目标中table?

为多列动态构建语句的基本查询 - 忽略 condition 列:

SELECT format(
      'INSERT INTO %I (%s) SELECT %s FROM %I'
     , tbl_des
     , string_agg(quote_ident(col_des), ', ')
     , string_agg(quote_ident(col_src), ', ')
     , tbl_src) AS sql
FROM   table3
WHERE  tbl_des = 'Table2'
AND    tbl_src = 'Table1'
GROUP  BY tbl_des, tbl_src;

结果:

INSERT INTO "Table2" (col1, col2, col3)
SELECT CASE col1, col2, col3 FROM "Table1"

这假设一个单一来源和一个单一 目的地 table。或者事情变得更复杂。我添加了 WHERE 条件来说明这一点。我添加到 的区分大小写的注释仍然适用。

上面还是忽略了condition。首先,不要在 condition 列中包含关键字 WHERE。这只是噪音,没有帮助:

INSERT INTO Table3(tbl_src, col_src, tbl_des, col_des, condition)
VALUES ('Table1','col1','Table2','col1', <b>'col1>=1'</b>)  -- without WHERE!
     , ('Table1', ...

警告

这种方法本质上是不安全的。 condition 包含需要“按原样”连接的表达式,因此您完全可以接受 SQL 注入攻击 。您需要确保不受信任的用户不能以任何方式写入 table3 以避免这种情况。

在此基础上,假设每个条件 仅适用于其各自的列 ,我们可以通过将列包装在 CASE 表达式中来解决它:

SELECT format(
      'INSERT INTO %I (%s) SELECT %s FROM %I'
     , tbl_des
     , string_agg(quote_ident(col_des), ', ')
     , string_agg(
         CASE WHEN condition IS NULL
            THEN quote_ident(col_src)
            ELSE format('CASE WHEN %s THEN %I END'
                 , condition, col_src)  -- condition is unsafe!
         END, ', ')
     , tbl_src) AS sql
FROM   table3
WHERE  tbl_des = 'Table2'
AND    tbl_src = 'Table1'
GROUP  BY tbl_des, tbl_src;

生成以下形式的语句:

INSERT INTO "Table2" (col1, col2, col3)
SELECT CASE WHEN col1>=1 THEN col1 END, col2, col3 FROM "Table1"

或者,就像您在稍后的评论中添加的那样,条件可以应用于整行。从逻辑上讲,这是另一个灰色地带。该条件存储在特定列中,但适用于整行 ...

尽管如此,您可以在 WHERE 子句中添加通用条件。

SELECT format(
      'INSERT INTO %I (%s) SELECT %s FROM %I%s'
     , tbl_des
     , string_agg(quote_ident(col_des), ', ')
     , string_agg(quote_ident(col_src), ', ')
     , tbl_src
     , ' WHERE ' || string_agg(condition, ' AND ')) AS sql
FROM   table3
WHERE  tbl_des = 'Table2'
AND    tbl_src = 'Table1'
GROUP  BY tbl_des, tbl_src;

条件是 AND 运算,并且 WHERE 子句仅在存在任何条件时附加 - 否则生成的 NULL 值会吞没表达式 ' WHERE ' || string_agg(condition, ' AND '))

中添加的关键字

DO 命令或 plpgsql 函数中使用它以按照我之前的回答中的指示动态执行:

基本plpgsql函数:

CREATE OR REPLACE FUNCTION f_test()
  RETURNS void AS
$func$
BEGIN
   EXECUTE (
   SELECT format(
         'INSERT INTO %I (%s) SELECT %s FROM %I%s'
        , tbl_des
        , string_agg(quote_ident(col_des), ', ')
        , string_agg(quote_ident(col_src), ', ')
        , tbl_src
        , ' WHERE ' || string_agg(condition, ' AND ')) AS sql
   FROM   table3
   WHERE  tbl_des = 'Table2'
   AND    tbl_src = 'Table1'
   GROUP  BY tbl_des, tbl_src
   );
END
$func$ LANGUAGE plpgsql;

SQL Fiddle.