PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Condição de corrida entre SELECT e INSERT para várias colunas


A chave em andamento é SELECT f_insert_tag(tag_p_id, _tag) ao invés de

SELECT * FROM f_insert_tag(tag_p_id, _tag)

Para 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;

db<>fiddle aqui

Para Postgres 9.5 ou posterior:

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;

db<>fiddle aqui

Básico aqui: