使用带有 psql 变量作为参数的表达式创建序列

M0l*_*les 4 postgresql psql sequence dynamic-sql ddl

我已经用 Postgres 玩了一个星期左右,我正在看看是否可以使用表达式设置序列的最小值/最大值。具体目标是为服务器之间的不相交范围自动创建脚本,以避免多主设置中的密钥冲突。

例如,使用 psql:

CREATE SEQUENCE key_seq MINVALUE (:servernum * :stride)
                        MAXVALUE ((:servernum + 1) * :stride - 1);
Run Code Online (Sandbox Code Playgroud)

这给了我一个语法错误。变量是逐字内插的,不进行计算。

另一种方法是根据表达式的输出设置变量:

\set minvalue (:servernum * :stride)
Run Code Online (Sandbox Code Playgroud)

但该\set语句不计算表达式。

Erw*_*ter 5

您需要\set(不是\pset!)设置psql变量。与 Unix shell 中的赋值不同,psql 赋值不能进行算术运算。您可以使用 psql 命令\!来执行 shell 命令,但我会建议两种不同的方法:

选项 1:让 Postgres 计算并设置新变量 \gset

手册关于\gset

将当前查询输入缓冲区发送到服务器并将查询的输出存储到 psql 变量中

然后您可以将计算出的数字插入为文本,一切正常。
psql 代码:

test=# \set servernum 5
test=# \set stride 300
test=# SELECT :servernum * :stride AS minvalue, (:servernum + 1) * :stride - 1 AS maxvalue \gset
test=# CREATE SEQUENCE key_seq MINVALUE :minvalue MAXVALUE :maxvalue;
CREATE SEQUENCE
Run Code Online (Sandbox Code Playgroud)

一个很好的博客解释\gset

我计算和设置:minvalue:maxvalue在一个单一 SELECT的优化性能。尽管如此,这种方法的缺点是您需要额外往返 Postgres 服务器。

选项 2:具有动态 SQL 以供重复使用的函数

可以使用DO声明,但是(再次使用手册):

不会在引用的 SQL 文字和标识符内执行变量插值。

这会使字符串连接复杂化。我建议创建一个函数并format()用于干净的代码。如果它仅用于当前会话,您可以通过模式限定使其成为临时函数,pg_temp.因此该函数仅对您的当前会话可见,并在会话结束时删除。临时功能是一个未记录的功能 - 即使是由 Tom Lane 建议的。细节:

所以:

test=# CREATE FUNCTION pg_temp.f_my_seq(_seq text, _servernum int, _stride int)
  RETURNS void AS
$func$
BEGIN
   EXECUTE format('CREATE SEQUENCE %I MINVALUE %s MAXVALUE %s'
                 , _seq, _servernum * _stride, (_servernum + 1) * _stride - 1);
END
$func$ LANGUAGE plpgsql;

test=# \set servernum 5
test=# \set stride 300

test=# SELECT pg_temp.f_my_seq('key_seq', :servernum, :stride);
Run Code Online (Sandbox Code Playgroud)

我们需要动态 SQL 与EXECUTE. 关于变量替换的手册:

变量替换目前仅适用于SELECTINSERTUPDATEDELETE命令,因为主 SQL 引擎仅允许在这些命令中使用查询参数。要在其他语句类型(通常称为实用程序语句)中使用非常量名称或值,您必须将实用程序语句构造为字符串和EXECUTE它。

SO的相关答案: