“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 ALL
或VALUES
表达式而不是UNION
。但整个方法是有缺陷的。什么样的任务规范需要不充分的技术?
@ErwinBrandstetter 我们正在将数据库从 MS SQL 重写为 Postgre。 MS SQL 有过程,因此 1 个过程可以返回多个表。 Postgre 不能,所以我们决定返回 setof refcursor,1 个 refcursor 用于 1 个表。这就是为什么第一个函数必须返回 refcursor(或 setof refcursor),而不是 table()。但我不知道将数据从 refcursor 插入临时表的适当方法。
【参考方案1】:
很遗憾,INSERT
和 SELECT
不能作为一个整体访问游标。
为了避免昂贵的单行INSERT
,您可以使用RETURNS TABLE
使用中间函数,并使用RETURN QUERY
将游标作为表返回。见:
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 ...”无法编译的主要内容,如果未能解决你的问题,请参考以下文章