Состояние гонки между SELECT и INSERT для нескольких столбцов - PullRequest
1 голос
/ 14 мая 2019

Примечание: это вопрос, который является продолжением этого решения . Вам нужно прочитать ссылку, чтобы получить контекст для этого вопроса. Также это для postgres v9.4

Если мы хотим вернуть несколько столбцов, а не только 1 столбец, как мы можем достичь этого?

Давайте возьмем таблицу t:

 create table t(tag_id int, tag text unique);

Теперь вот что я хочу: всякий раз, когда я вызываю метод f_tag_id, я хочу, чтобы он возвращал все столбцы для уникальной строки, если она существует в таблице t, иначе вставьте ее и верните все столбцы.

Так вот, что я пытался для f_insert_tag

Option1:

CREATE OR REPLACE FUNCTION f_insert_tag(tag_p_id int, _tag text)
RETURNS TABLE(_tag_p_id int, _tag_ text) 
AS
$func$
    BEGIN
        INSERT INTO t(tag_id,tag) VALUES (tag_p_id, _tag);
        return query Select * from t where t.tag_id = tag_p_id;
        EXCEPTION WHEN UNIQUE_VIOLATION THEN  -- catch exception, NULL is returned
    END
$func$ LANGUAGE plpgsql;

Вариант 2:

   CREATE OR REPLACE FUNCTION f_insert_tag(tag_p_id int, _tag text, out _tag_id int, out _tag_ text) 
AS
$func$
    BEGIN
        INSERT INTO t(tag_id,tag) VALUES (tag_p_id, _tag);
        Select t.tag_id, t.tag from t where t.tag_id = tag_p_id into _tag_id, _tag_;
        EXCEPTION WHEN UNIQUE_VIOLATION THEN  -- catch exception, NULL is returned
    END
$func$ LANGUAGE plpgsql;

Вариант 3:

CREATE OR REPLACE FUNCTION f_insert_tag(tag_p_id int, _tag text)
returns setof t AS
$func$
  BEGIN
    INSERT INTO t(tag_id,tag) VALUES (tag_p_id, _tag);
    return query Select * from t where t.tag_id = tag_p_id;
    EXCEPTION WHEN UNIQUE_VIOLATION THEN  -- catch exception, NULL is returned
  END
$func$ LANGUAGE plpgsql;

Все 3 работали сами по себе:

select f_insert_tag(1322, 'helloworldaa');
    f_insert_tag
---------------------
 (1322,helloworldaa)

Для другой функции, f_tag_id, я перепробовал много методов:

Вариант 1:

CREATE OR REPLACE FUNCTION f_tag_id(tag_p_id int, _tag text, out _tag_id int, out _tag_ text) 
AS
$func$
BEGIN
   LOOP
      SELECT t.tag_id, t.tag FROM t WHERE t.tag = _tag
      UNION ALL
      SELECT f_insert_tag(tag_p_id, _tag)
      into _tag_id, _tag_;

      EXIT WHEN _tag_id IS NOT NULL;  -- else keep looping
   END LOOP;
END
$func$ LANGUAGE plpgsql;

Вариант 2:

CREATE OR REPLACE FUNCTION f_tag_id(tag_p_id int, _tag text)
RETURNS table(_tag_id int, _tag_ text) AS
$func$

BEGIN
   LOOP
      SELECT t.tag_id, t.tag FROM t WHERE t.tag = _tag
      UNION  ALL
      SELECT f_insert_tag(tag_p_id, _tag)
      into _tag_id, _tag_;

      EXIT WHEN _tag_id IS NOT NULL;  -- else keep looping
   END LOOP;
END
$func$ LANGUAGE plpgsql;

Для обоих я получил одну и ту же ошибку:

select f_tag_id(22, 'test');
ERROR:  each UNION query must have the same number of columns
LINE 3:       SELECT f_insert_tag(tag_p_id, _tag)
                     ^
QUERY:  SELECT t.tag_id, t.tag FROM t WHERE t.tag = _tag
      UNION  ALL
      SELECT f_insert_tag(tag_p_id, _tag)
CONTEXT:  PL/pgSQL function f_tag_id(integer,text) line 5 at SQL statement

1 Ответ

1 голос
/ 14 мая 2019

Ключ в работах: SELECT f_insert_tag(tag_p_id, _tag) вместо

SELECT * FROM f_insert_tag(tag_p_id, _tag)

Для Postgres 9,4

CREATE FUNCTION f_insert_tag(_tag_id int, _tag text, OUT _tag_id_ int, OUT _tag_ text) 
AS
$func$
 BEGIN
   INSERT INTO t(tag_id, tag)
   VALUES (_tag_id, _tag)
   RETURNING t.tag_id, t.tag
   INTO  _tag_id_, _tag_;

   EXCEPTION WHEN UNIQUE_VIOLATION THEN
      -- catch exception, return NULL
 END
$func$  LANGUAGE plpgsql;


CREATE FUNCTION f_tag_id(_tag_id int, _tag text, OUT _tag_id_ int, OUT _tag_ text) AS
$func$
BEGIN
LOOP
   SELECT t.tag_id, t.tag
   FROM   t
   WHERE  t.tag = _tag

   UNION ALL
   SELECT *                                              -- !!!
   FROM   f_insert_tag(_tag_id, _tag)
   LIMIT  1

   INTO _tag_id_, _tag_;

   EXIT WHEN _tag_id_ IS NOT NULL;  -- else keep looping
END LOOP;
END
$func$ LANGUAGE plpgsql;

дБ <> скрипка здесь

Для Postgres 9,5 или новее:

CREATE FUNCTION f_tag_id(_tag_id int, _tag text, OUT _tag_id_ int, OUT _tag_ text) AS
$func$
BEGIN
LOOP
   SELECT t.tag_id, t.tag
   FROM   t
   WHERE  t.tag = _tag
   INTO   _tag_id_, _tag_;

   EXIT WHEN FOUND;

   INSERT INTO t (tag_id, tag)
   VALUES (_tag_id, _tag)
   ON     CONFLICT (tag) DO NOTHING
   RETURNING t.tag_id, t.tag
   INTO   _tag_id_, _tag_;

   EXIT WHEN FOUND;
END LOOP;
END
$func$  LANGUAGE plpgsql;

дБ <>скрипка здесь

Основы здесь:

...