Postgres函数创建唯一的字符串并插入表中

问题描述

我在Postgres中有一个表,其中包含用户ID和外部ID的列,这些列将在对外部服务的API调用中使用。我必须在自己的一侧创建外部ID,验证其唯一性,然后在调用外部API之前将其存储在PG中。 本文与我想要的内容关闭How can I generate a unique string per record in a table in Postgres? 但是,如果两个并发调用生成相同的ID,则可能会发生冲突。我想做的是有一个生成随机字符串的循环,然后尝试将带有用户ID的字符串插入表中。如果随机字符串已经存在(列上有唯一约束),它将失败。如果失败,它将生成一个id并尝试插入该ID(一旦获得工作代码,我将添加一个计数器以防止锤击数据库)。

您将如何编写该循环?如果INSERT返回错误(约束检查),则循环应继续进行,否则将再次循环。我已经检查了Postgres文档,但似乎找不到(或缺少)检查查询中的错误代码/状态的方法

更新

我想出了一个可能的解决方案,但需要充实它。以下是在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列中给出强烈唯一的(数据库范围内的)字符串

但是,如果您唯一的选择是尝试循环,则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;
,

修订版: 您对使用序列的安全性担忧可能有一定的有效性,尽管我不记得有一次甚至在安全性审核中也会出现这种情况。但是,如果这是一项业务需求,那么您必须遵循它。在我看来,您需要处理多个表的键冲突,因此,针对每个表使用特定的插入函数,通用化的通用生成似乎是合适的。您将需要为每个表编写插入函数,并且不能仅使用插入语句,必须使用函数(如果使用的是Postgres V12或更高版本,则必须使用过程)。您还必须将每一列作为参数传递给insert函数。以下基本上是“充实”您的伪代码。

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

您当然可以放弃这个想法,或者实际上放弃两个ID。

原始: 因此,面向外部的ID必须是唯一的,但为什么要随机。从序列中生成ID,然后将序列最大值限制为9999999999。然后将生成的序列转换为文本并存储该结果。这样,内部和外部ID都是唯一的,但具有相同的值(至少在外部强制转换类型为ID时)。更好的是,如果您拥有Postgres 12或更高版本,则可以将外部ID定义为ID上生成的列,从而保证它们始终相同。表的定义变为:

create table atable
             ( id integer  generated always as identity (maxvalue 999999999),ext_id text generated always as  (id::text) stored,colx text 
             ) ;

请参见demo。注意:演示将id定义为“默认情况下生成”。这仅用于演示目的。