序列名称取自变量
Posted
技术标签:
【中文标题】序列名称取自变量【英文标题】:Sequence name taken from variable 【发布时间】:2014-09-19 11:09:36 【问题描述】:如何创建一个新序列,其名称来自变量?
我们来看下面的例子:
CREATE OR REPLACE FUNCTION get_value(_name_part character varying)
RETURNS INTEGER AS
$BODY$
DECLARE
result bigint;
sequencename character varying(50);
BEGIN
sequencename = CONCAT('constant_part_of_name_', _name_part);
IF((SELECT CAST(COUNT(*) AS INTEGER) FROM pg_class
WHERE relname LIKE sequencename) = 0)
THEN
CREATE SEQUENCE sequencename --here is the guy this is all about
MINVALUE 6000000
INCREMENT BY 1;
END IF;
SELECT nextval(sequencename) INTO result;
RETURN result;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
现在,假设我想要一个 _name_part = 'Whatever' 的序列,所以我输入:
SELECT get_value('Whatever');
如果序列constant_part_of_name_Whatever
不存在,我的函数应该创建它并取一个值;如果它存在,它应该只取一个值。但是,我创建了序列constant_part_of_name_sequencename
。
如何将变量的值放入序列定义中以使其工作?
【问题讨论】:
【参考方案1】:当前接受的答案存在许多问题。最重要的是,它没有考虑到 schema。
改用:
CREATE OR REPLACE FUNCTION get_value(_name_part text)
RETURNS bigint AS
$func$
DECLARE
_seq text := 'constant_part_of_name_' || _name_part;
BEGIN
CASE (SELECT c.relkind = 'S'::"char"
FROM pg_namespace n
JOIN pg_class c ON c.relnamespace = n.oid
WHERE n.nspname = current_schema() -- or provide your schema!
AND c.relname = _seq)
WHEN TRUE THEN -- sequence exists
-- do nothing
WHEN FALSE THEN -- not a sequence
RAISE EXCEPTION '% is not a sequence!', _seq;
ELSE -- sequence does not exist, name is free
EXECUTE format('CREATE SEQUENCE %I MINVALUE 6000000 INCREMENT BY 1', _seq);
END CASE;
RETURN nextval(_seq);
END
$func$ LANGUAGE plpgsql;
SQL Fiddle.
要点
您的测试不必要地昂贵且不正确。您需要考虑架构。同名的序列可以存在于另一个模式中,这会使您的函数失败。 我使用当前模式作为默认模式,因为您没有另外指定。细节: How does the search_path influence identifier resolution and the "current schema" 您还需要注意,序列的名称与同一架构中其他对象的其他名称冲突。细节: How to create sequence if not existsvarchar(50)
因为数据类型没有意义,如果输入更长的字符串可能会导致问题。只需使用text
或varchar
。
The assignment operator in plpgsql is :=
, not =
.
您可以在声明时分配变量。更短、更便宜、更清洁。
您需要动态 SQL,我使用 format()
和 %I
来正确转义标识符。详情:
concat()
仅在可以涉及 NULL
值时才有用。我假设你不想通过NULL
。
VOLATILE
是默认值,因此只是噪音。
如果要在 NULL 输入上返回 NULL,请添加 STRICT
。
【讨论】:
我的函数很简单,因为我运行的数据库并不复杂。但是,从一开始就正确地写东西绝对是一个好习惯,所以我将使用您的答案并查看您链接中提供的主题。感谢您的回答。 @spoko:我再次更新了函数。速度和优雅度稍高一些。添加了小提琴和更多解释。 通过pg_*
视图检查存在时存在固有的竞争条件。无论如何都没有必要,您可以只调用nextval(seq)
并在异常子句中处理不存在错误。如果另一个会话忙于删除或创建相同的序列,至少 nextval
会阻塞。
@DanielVérité:引发异常不会消除竞争条件。并且,quoting the manual:A block containing an EXCEPTION clause is significantly more expensive to enter and exit than a block without one. Therefore, don't use EXCEPTION without need.
会扩大比赛的时间范围。可以将FOR SHARE
添加到SELECT
以防止并发DELETE
(从未尝试过)。但是没有针对并发CREATE SEQUENCE
的药物。最好保持小额交易...
@ErwinBrandstetter 感谢您的参与。我知道已经过去了一段时间,但我还是希望你能看到这一点。上面的代码在 Postgres 9.3 上工作得很好。但是,当我在 Postgres 8.3.23 中运行它时,我得到 syntax error at or near "CASE"
和 ^ 标记指向代码中 CASE 单词的开头。您知道我需要添加/删除/更改什么才能使其正常工作吗?【参考方案2】:
试试这个。希望这对你有用。
CREATE OR REPLACE FUNCTION get_value(_name_part character varying) RETURNS INTEGER AS
$BODY$
DECLARE
result bigint;
sequencename character varying(50);
v_sql character varying;
BEGIN
sequencename = CONCAT('constant_part_of_name_', _name_part);
IF((SELECT CAST(COUNT(*) AS INTEGER) FROM pg_class WHERE relname LIKE sequencename) = 0)
THEN
v_sql := 'CREATE SEQUENCE '||sequencename||'
MINVALUE 6000000
INCREMENT BY 1;';
EXECUTE v_sql;
END IF;
SELECT nextval(sequencename) INTO result ;
RETURN result;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
【讨论】:
它做到了,这就是我想要的。但是,我允许自己稍微改进您的答案,希望您不要介意;)。 这个函数还有很多问题。我提供了另一个答案。【参考方案3】:CREATE OR REPLACE FUNCTION get_value(_name_part character varying) RETURNS INTEGER AS
$BODY$
DECLARE
result bigint;
sequencename character varying(50);
BEGIN
sequencename = CONCAT('constant_part_of_name_', _name_part);
IF (select exists(SELECT relname FROM pg_class c WHERE c.relkind = 'S' and relname = ''''||sequencename||'''') = false )
THEN
execute 'CREATE SEQUENCE '||sequencename||'MINVALUE 6000000 INCREMENT BY 1';
else
END IF;
execute 'SELECT nextval('''||sequencename||''')' INTO result;
RETURN result;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
【讨论】:
以上是关于序列名称取自变量的主要内容,如果未能解决你的问题,请参考以下文章