“INSERT INTO ... FETCH ALL FROM ...”无法编译

Posted

技术标签:

【中文标题】“INSERT INTO ... FETCH ALL FROM ...”无法编译【英文标题】:"INSERT INTO ... FETCH ALL FROM ..." can't be compiled 【发布时间】:2018-06-13 12:40:23 【问题描述】:

我在 PostgreSQL 9.6 上有一些函数返回一个游标 (refcursor):

CREATE OR REPLACE FUNCTION public.test_returning_cursor()
  RETURNS refcursor
IMMUTABLE
LANGUAGE plpgsql
AS $$
DECLARE
  _ref refcursor = 'test_returning_cursor_ref1';
BEGIN
  OPEN _ref FOR
  SELECT 'a' :: text AS col1
  UNION
  SELECT 'b'
  UNION
  SELECT 'c';

  RETURN _ref;
END
$$;

我需要编写另一个函数,在其中创建一个临时表,并将来自该refcursor 的所有数据插入其中。但是INSERT INTO ... FETCH ALL FROM ... 似乎是不可能的。该函数无法编译:

CREATE OR REPLACE FUNCTION public.test_insert_from_cursor()
  RETURNS table(col1 text)
IMMUTABLE
LANGUAGE plpgsql
AS $$
BEGIN
  CREATE TEMP TABLE _temptable (
    col1 text
  ) ON COMMIT DROP;

  INSERT INTO _temptable (col1)
  FETCH ALL FROM "test_returning_cursor_ref1";

  RETURN QUERY
  SELECT col1
  FROM _temptable;
END
$$;

我知道我可以使用:

FOR _rec IN
  FETCH ALL FROM "test_returning_cursor_ref1"
LOOP
  INSERT INTO ...
END LOOP;

但是有更好的方法吗?

【问题讨论】:

如果第一个函数也定义为returns table (...)会容易很多 @a_horse_with_no_name 我认为这是最好的解决方案。 第一个函数必须按任务规范返回refcursor。 旁白:在第一个查询中使用UNION ALLVALUES 表达式而不是UNION。但整个方法是有缺陷的。什么样的任务规范需要不充分的技术? @ErwinBrandstetter 我们正在将数据库从 MS SQL 重写为 Postgre。 MS SQL 有过程,因此 1 个过程可以返回多个表。 Postgre 不能,所以我们决定返回 setof refcursor,1 个 refcursor 用于 1 个表。这就是为什么第一个函数必须返回 refcursor(或 setof refcursor),而不是 table()。但我不知道将数据从 refcursor 插入临时表的适当方法。 【参考方案1】:

很遗憾,INSERTSELECT 不能作为一个整体访问游标。

为了避免昂贵的单行INSERT,您可以使用RETURNS TABLE 使用中间函数,并使用RETURN QUERY 将游标作为表返回。见:

Return a query from a function?
CREATE OR REPLACE FUNCTION f_cursor1_to_tbl()
  RETURNS TABLE (col1 text) AS
$func$
BEGIN
   -- MOVE BACKWARD ALL FROM test_returning_cursor_ref1;  -- optional, see below

   RETURN QUERY
   FETCH ALL FROM test_returning_cursor_ref1;
END
$func$  LANGUAGE plpgsql;  -- not IMMUTABLE

然后直接创建临时表,比如:

CREATE TEMP TABLE t1 ON COMMIT DROP
AS SELECT * FROM f_cursor1_to_tbl();

见:

Creating temporary tables in SQL

仍然不是很优雅,但比单行 INSERT很多

注意:由于源是cursor,因此只有第一次调用成功。再次执行该函数将返回一个空集。您需要一个带有 SCROLL option 的光标,然后移动到开头以重复调用。

【讨论】:

但是这样的中介函数怎么写呢?它必须从游标和返回表中读取,所以它会有 SELECT...FROM FETCH ALL... ,但是这样的构造是不允许的。我只知道一种解决方法,但这种方法很糟糕。 谢谢,我用“RETURN QUERY FETCH ALL FROM ...”编写了中间函数来将游标的数据转换到表中,并且成功了。 @ЕвгенийНоздрев:是的。并简化您的临时表创建。我在上面添加了一些。【参考方案2】:

此函数从refcursor 执行INSERT INTO。它对所有表格都是通用。唯一的要求是 table 的所有列都按类型和顺序对应于 refcursor 的列(不需要按名称)。

to_json() 可以将任何原始数据类型转换为带有双引号 "" 的字符串,随后将其替换为 ''

CREATE OR REPLACE FUNCTION public.insert_into_from_refcursor(_table_name text, _ref refcursor)
  RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
  _sql       text;
  _sql_val   text = '';
  _row       record;
  _hasvalues boolean = FALSE;
BEGIN

  LOOP   --for each row
    FETCH _ref INTO _row;
    EXIT WHEN NOT found;   --there are no rows more

    _hasvalues = TRUE;

    SELECT _sql_val || '
           (' ||
           STRING_AGG(val.value :: text, ',') ||
           '),'
        INTO _sql_val
    FROM JSON_EACH(TO_JSON(_row)) val;
  END LOOP;

  _sql_val = REPLACE(_sql_val, '"', '''');
  _sql_val = TRIM(TRAILING ',' FROM _sql_val);

  _sql = '
          INSERT INTO ' || _table_name || '
          VALUES ' || _sql_val;
  --RAISE NOTICE 'insert_into_from_refcursor(): SQL is: %', _sql;
  IF _hasvalues THEN    --to avoid error when trying to insert 0 values
    EXECUTE (_sql);
  END IF;
END;
$$;

用法:

CREATE TABLE public.table1 (...);
PERFORM my_func_opening_refcursor();
PERFORM public.insert_into_from_refcursor('public.table1', 'name_of_refcursor_portal'::refcursor);

my_func_opening_refcursor() 包含在哪里

DECLARE
  _ref refcursor = 'name_of_refcursor_portal';

OPEN _ref FOR
SELECT ...;

【讨论】:

以上是关于“INSERT INTO ... FETCH ALL FROM ...”无法编译的主要内容,如果未能解决你的问题,请参考以下文章