用户写下他的名字,我想将其存储到数据库中。如果名称已经在数据库中,我想插入一个后缀。即将'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
有什么建议吗?
答案 0 :(得分:1)
如果您需要担心并发性,保证唯一性的最简单方法是发出insert语句直到成功。 (这假设你当然有一个独特的约束。)
伪代码:
while true
if db.execute(insert_sql, [..., name + postfix, ...])
break
end
counter += 1
postfix = '_' + counter
end
您可以通过从现有的最大后缀开始,在较短的时间内运行该过程(请参阅其他答案,并采用相应的方法)。
一个尴尬的替代方法是使用select语句找到最大的现有后缀,然后尝试在适用的名称和后缀的唯一内容上获取advisory lock,例如: 'username:' + name + postfix
。但它不那么健壮,因为它开启了两个事务找到相同max_postfix的可能性,然后一个事务尝试在其他事务完成后立即获取锁定提交其插入并释放该锁定 - 从而导致重复。
答案 1 :(得分:0)
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;
答案 2 :(得分:0)
SELECT CASE WHEN num IS NULL THEN 'John' ELSE 'John' || '_' || num END AS new_name
FROM (
SELECT max(substr(my_name, position('_' in my_name) + 1)::int) + 1 AS num
FROM all_names
WHERE my_name ilike 'John' || '_%'
) new_number
所有三个' John'在您输入的名称传递的位置。 (这假设用户不能在其名称下面加下划线,而且数字将始终跟在下划线上。)
编辑:这也是假设' John'和' john'应该一视同仁。如果他们不应该,那么用呃代替ilike。