Postgres 函数创建唯一字符串并插入 table
Postgres function create unique string and insert into table
我在 Postgres 中有一个 table,其中包含用于 API 调用外部服务的用户 ID 和外部 ID 的列。我必须在我这边创建外部 id,验证它是唯一的,并在调用外部 API 之前将其存储在 PG 中。
本文接近我想要的:
How can I generate a unique string per record in a table in Postgres?
但是,如果两个并发调用生成相同的 id,则可能会发生冲突。我想要做的是有一个生成随机字符串的循环,然后尝试将其与用户 ID 一起插入到 table 中。如果随机字符串已经存在(列上有唯一约束),它应该会失败。如果失败,它应该生成另一个 ID 并尝试插入它(一旦我得到工作代码,我将添加一个计数器以防止破坏数据库)。
您将如何编写该循环?如果 INSERT returns 出错(约束检查),循环应该继续,否则再次循环。我检查了 Postgres 文档,但似乎找不到(或找不到)检查查询错误 code/status 的方法。
更新
我想出了一个可能的解决方案,但需要充实它。以下是pidgeon-sql,只是我在思考问题:
success = true;
LOOP
-- create random string function
BEGIN
insert string
EXCEPTION
success = false;
EXIT WHEN success;
END;
如果不需要外部ID的随机性,那么
CREATE SEQUENCE base_seq;
ALTER TABLE thetable
ALTER COLUMN ext_id SET DEFAULT LPAD(nextval('base_seq')::text, 64, '0');
将在 ext_id 列
中提供非常独特的(数据库范围内的)字符串
但是如果你唯一的选择是try-in-a-loop,那么plpgsql函数中的循环将是这样的:
LOOP
new_try_ext_id := some randomization magic here...
INSERT INTO thetable(userid,ext_id)
VALUES (someid, new_try_ext_id)
ON CONFLICT DO NOTHING;
GET DIAGNOSTICS some_integer_var = ROW_COUNT;
EXIT WHEN some_integer_var > 0;
END LOOP;
修订:
您对使用序列的安全担忧可能有一定的道理,尽管我不记得即使在安全审计中也会出现这种情况。但是,如果这是一项业务需求,那么您必须接受它。在我看来,您需要处理多个 table 的键冲突,因此广义函数广义生成似乎适合每个 table 的特定插入函数。您需要为每个 table 和 编写插入函数,不能 只使用插入语句,您必须使用一个函数(或者过程,如果您使用的是 Postgres V12 或更高版本) .您还必须将每一列作为参数传递给插入函数。下面基本上'flesh out'你的pseudo-code.
create or replace function generate_random_id
( lower_value_in bigint default 1
, upper_value_in bigint default 10000000000)
returns bigint
language sql
volatile strict
as $$
select floor(random()*(upper_value_in-lower_value_in+1)+1)::bigint ;
$$;
create or replace function insert_atable(col_x_in atable.colx%type)
returns void
language plpgsql
as $$
declare
l_invalid_id boolean := true;
begin
while l_invalid_id
loop
begin
insert into atable( id, colx)
values ( generate_random_id(),col_x_in);
l_invalid_id := false;
exception
when unique_violation then null;
end;
end loop;
end;
$$;
已修订 demo。
当然你可以放弃这个想法或者2个id实际上是一样的
原文:
所以面向外部的 id 必须是唯一的,但为什么是随机的。然后从序列生成 id,将该序列最大值限制为 9999999999。然后将生成的序列转换为文本并存储该结果。这样内部和外部 id 都是唯一的但具有相同的值(至少当外部类型为 id 时)。更好的是,如果你有更高版本的 Postgres 12,你可以将外部 id 定义为 id 上的生成列,从而保证它们始终相同。 Table 定义变成这样:
create table atable
( id integer generated always as identity (maxvalue 999999999)
, ext_id text generated always as (id::text) stored
, colx text
) ;
参见 demo。注意:Demo 将 id 定义为“默认生成”。这仅用于演示目的。
我在 Postgres 中有一个 table,其中包含用于 API 调用外部服务的用户 ID 和外部 ID 的列。我必须在我这边创建外部 id,验证它是唯一的,并在调用外部 API 之前将其存储在 PG 中。 本文接近我想要的: How can I generate a unique string per record in a table in Postgres? 但是,如果两个并发调用生成相同的 id,则可能会发生冲突。我想要做的是有一个生成随机字符串的循环,然后尝试将其与用户 ID 一起插入到 table 中。如果随机字符串已经存在(列上有唯一约束),它应该会失败。如果失败,它应该生成另一个 ID 并尝试插入它(一旦我得到工作代码,我将添加一个计数器以防止破坏数据库)。
您将如何编写该循环?如果 INSERT returns 出错(约束检查),循环应该继续,否则再次循环。我检查了 Postgres 文档,但似乎找不到(或找不到)检查查询错误 code/status 的方法。
更新
我想出了一个可能的解决方案,但需要充实它。以下是pidgeon-sql,只是我在思考问题:
success = true;
LOOP
-- create random string function
BEGIN
insert string
EXCEPTION
success = false;
EXIT WHEN success;
END;
如果不需要外部ID的随机性,那么
CREATE SEQUENCE base_seq;
ALTER TABLE thetable
ALTER COLUMN ext_id SET DEFAULT LPAD(nextval('base_seq')::text, 64, '0');
将在 ext_id 列
中提供非常独特的(数据库范围内的)字符串但是如果你唯一的选择是try-in-a-loop,那么plpgsql函数中的循环将是这样的:
LOOP
new_try_ext_id := some randomization magic here...
INSERT INTO thetable(userid,ext_id)
VALUES (someid, new_try_ext_id)
ON CONFLICT DO NOTHING;
GET DIAGNOSTICS some_integer_var = ROW_COUNT;
EXIT WHEN some_integer_var > 0;
END LOOP;
修订: 您对使用序列的安全担忧可能有一定的道理,尽管我不记得即使在安全审计中也会出现这种情况。但是,如果这是一项业务需求,那么您必须接受它。在我看来,您需要处理多个 table 的键冲突,因此广义函数广义生成似乎适合每个 table 的特定插入函数。您需要为每个 table 和 编写插入函数,不能 只使用插入语句,您必须使用一个函数(或者过程,如果您使用的是 Postgres V12 或更高版本) .您还必须将每一列作为参数传递给插入函数。下面基本上'flesh out'你的pseudo-code.
create or replace function generate_random_id
( lower_value_in bigint default 1
, upper_value_in bigint default 10000000000)
returns bigint
language sql
volatile strict
as $$
select floor(random()*(upper_value_in-lower_value_in+1)+1)::bigint ;
$$;
create or replace function insert_atable(col_x_in atable.colx%type)
returns void
language plpgsql
as $$
declare
l_invalid_id boolean := true;
begin
while l_invalid_id
loop
begin
insert into atable( id, colx)
values ( generate_random_id(),col_x_in);
l_invalid_id := false;
exception
when unique_violation then null;
end;
end loop;
end;
$$;
已修订 demo。
当然你可以放弃这个想法或者2个id实际上是一样的
原文: 所以面向外部的 id 必须是唯一的,但为什么是随机的。然后从序列生成 id,将该序列最大值限制为 9999999999。然后将生成的序列转换为文本并存储该结果。这样内部和外部 id 都是唯一的但具有相同的值(至少当外部类型为 id 时)。更好的是,如果你有更高版本的 Postgres 12,你可以将外部 id 定义为 id 上的生成列,从而保证它们始终相同。 Table 定义变成这样:
create table atable
( id integer generated always as identity (maxvalue 999999999)
, ext_id text generated always as (id::text) stored
, colx text
) ;
参见 demo。注意:Demo 将 id 定义为“默认生成”。这仅用于演示目的。