SELECT 和多列的 INSERT 之间的竞争条件
Race Condition between SELECT and INSERT for multiple columns
注意:这是 this solution 的后续问题。您需要阅读 link 以获取此问题的上下文。此外,这是针对 postgres v9.4
如果我们现在想 return 多列而不是只有 1 列,我们该如何实现?
我们来 table t:
create table t(tag_id int, tag text unique);
现在这就是我想要的:
每当我调用方法 f_tag_id 时,我希望它 return 唯一行的所有列(如果它存在于 table t
中,否则插入它并 return 所有列。
所以这些是我为 f_insert_tag
尝试的东西
选项 1:
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
作品中的扳手是 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;
db<>fiddle here
对于 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;
db<>fiddle here
这里是基础知识:
- Is SELECT or INSERT in a function prone to race conditions?
注意:这是 this solution 的后续问题。您需要阅读 link 以获取此问题的上下文。此外,这是针对 postgres v9.4
如果我们现在想 return 多列而不是只有 1 列,我们该如何实现?
我们来 table t:
create table t(tag_id int, tag text unique);
现在这就是我想要的:
每当我调用方法 f_tag_id 时,我希望它 return 唯一行的所有列(如果它存在于 table t
中,否则插入它并 return 所有列。
所以这些是我为 f_insert_tag
选项 1:
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
作品中的扳手是 而不是 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;
db<>fiddle here
对于 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;
db<>fiddle here
这里是基础知识:
- Is SELECT or INSERT in a function prone to race conditions?