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 定义为“默认生成”。这仅用于演示目的。