Postgresql 将值转换为唯一值(从John转换为John_1)

Postgresql 将值转换为唯一值(从John转换为John_1),postgresql,Postgresql,用户写下他的名字,我想把它存储到数据库中。如果名称已经在数据库中,我想插入后缀。即将“John”转换为介于('John_1'、'John_2'…等)之间的第一个 到目前为止,这是我的方法,但我相信还有更好的方法 select n from ( select 'John' n ,0 v union select 'John'||'_'||generate_series(1,100),generate_series(1,100) ) possible_names where n not in (se

用户写下他的名字,我想把它存储到数据库中。如果名称已经在数据库中,我想插入后缀。即将“John”转换为介于('John_1'、'John_2'…等)之间的第一个

到目前为止,这是我的方法,但我相信还有更好的方法

select n from
(
select 'John' n ,0 v
union
select 'John'||'_'||generate_series(1,100),generate_series(1,100)
) possible_names
where n not in
(select my_name from all_names u)
order by v
limit 1
有什么建议吗

CREATE FUNCTION get_username_proposal(text) RETURNS text AS $$
  SELECT
    CASE WHEN (SELECT COUNT(*) FROM all_names WHERE my_name = $1)=0 THEN
      $1
    ELSE
      $1 || '_' || COALESCE(MAX(LTRIM(SUBSTRING(my_name FROM '_[0-9]+$'), '_')::int), 0)+1
    END
  FROM
    all_names
  WHERE
    my_name ~ ($1 || '_[0-9]+$');
$$ LANGUAGE SQL STABLE;
“John”的三个实例都是你输入的名字。(这是假设用户不能将下划线作为其姓名的一部分,并且下划线后面始终会有一个数字。)


编辑:这也是假设“约翰”和“约翰”应该被同等对待。如果不应该,则用like替换ilike。

如果需要担心并发性,保证唯一性的最简单方法是发出insert语句,直到成功为止。(当然,这假设您有一个唯一的约束。)

伪代码:

while true
  if db.execute(insert_sql, [..., name + postfix, ...])
    break
  end
  counter += 1
  postfix = '_' + counter
end
通过从现有的最大后缀开始,您可以使该过程在更短的时间内运行(请参阅其他答案中的方法)

另一个尴尬的选择是使用select语句查找现有的最大后缀,然后尝试获取适用名称和后缀的唯一内容,例如,
'username:'+name+postfix
。但它的健壮性要差得多,因为它提供了两个事务找到相同的max_后缀的可能性,然后一个事务在另一个事务完成提交插入并释放锁后立即尝试获取锁,从而导致重复

while true
  if db.execute(insert_sql, [..., name + postfix, ...])
    break
  end
  counter += 1
  postfix = '_' + counter
end