"INSERT INTO ... FETCH ALL FROM ..."无法编译
"INSERT INTO ... FETCH ALL FROM ..." can't be compiled
我在 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
$$;
我需要编写另一个函数,在其中创建一个临时 table 并将来自此 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;
但是有更好的方法吗?
遗憾的是,INSERT
和 SELECT
无法访问整个游标。
为了避免昂贵的单行 INSERT
,您可以使用 RETURNS TABLE
和 return 游标作为 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
然后直接创建临时table(s)像:
CREATE TEMP TABLE t1 ON COMMIT DROP
AS SELECT * FROM f_cursor1_to_tbl();
参见:
- Creating temporary tables in SQL
仍然不是很优雅,但是 比单行快 很多 INSERT
。
注意:由于来源是 cursor
只有第一次调用成功。第二次执行该函数将 return 一个空集。您将需要一个带有 SCROLL
option 的光标并移动到重复调用的开头。
此函数执行 refcursor
中的 INSERT INTO
。对于所有 table,它是 通用的 。唯一的要求是 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 ...;
我在 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
$$;
我需要编写另一个函数,在其中创建一个临时 table 并将来自此 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;
但是有更好的方法吗?
遗憾的是,INSERT
和 SELECT
无法访问整个游标。
为了避免昂贵的单行 INSERT
,您可以使用 RETURNS TABLE
和 return 游标作为 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
然后直接创建临时table(s)像:
CREATE TEMP TABLE t1 ON COMMIT DROP
AS SELECT * FROM f_cursor1_to_tbl();
参见:
- Creating temporary tables in SQL
仍然不是很优雅,但是 比单行快 很多 INSERT
。
注意:由于来源是 cursor
只有第一次调用成功。第二次执行该函数将 return 一个空集。您将需要一个带有 SCROLL
option 的光标并移动到重复调用的开头。
此函数执行 refcursor
中的 INSERT INTO
。对于所有 table,它是 通用的 。唯一的要求是 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 ...;