序列名称取自变量

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 exists

varchar(50) 因为数据类型没有意义,如果输入更长的字符串可能会导致问题。只需使用textvarchar

The assignment operator in plpgsql is :=, not =.

您可以在声明时分配变量。更短、更便宜、更清洁。

您需要动态 SQL,我使用 format()%I 来正确转义标识符。详情:

INSERT with dynamic table name in trigger function

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

【讨论】:

以上是关于序列名称取自变量的主要内容,如果未能解决你的问题,请参考以下文章

在 SQL (Big Query) 中生成序列/范围/数组,其中最小值和最大值取自另一个表

将时间序列与变量具有相同名称的不同观察数量合并(SAS)

使用变量名数组反序列化 JSON

如何将序列中的下一个值放入变量中?

提升结构和类的融合序列类型和名称识别

SCCM 1906 任务序列自动根据SN命名计算机