"INSERT INTO ... FETCH ALL FROM ..." не может быть скомпилировано - PullRequest
0 голосов
/ 13 июня 2018

У меня есть какая-то функция в 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;

Но есть ли лучший способ?

Ответы [ 2 ]

0 голосов
/ 19 октября 2018

Эта функция делает INSERT INTO из refcursor. универсальный для всех таблиц.Единственное требование состоит в том, чтобы все столбцы таблицы соответствовали столбцам 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 ...;
0 голосов
/ 14 июня 2018

К сожалению, 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

Затем создать временную таблицу (таблицы) напрямую like:

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

См .:

Все еще не очень элегантно, но *На 1030 * намного быстрее, чем на одну строку INSERT.

Примечание. Поскольку источником является cursor, только первый вызов будет успешным.Выполнение функции во второй раз вернет пустой набор.Вам потребуется курсор с параметром SCROLL и переход к началу для повторных вызовов.

...