在PostgresQL中将序列更新为下一个可用的序列

时间:2012-10-03 16:54:25

标签: database postgresql sequence auto-increment

好的,我正在PostgresQL中创建用户数据库,我已经使用了一两个月,但我不是很熟悉。目前,通过PostgresQL序列自动为每个注册用户分配一个唯一ID。这一直是惯例,并且一直很好,但是...... 我有来自客户端的请求,可以手动输入用户的某些ID。我预计不会超过300个用户,所以我为手动输入的ID保留了ID 500-600。 (如果他们在线注册,他们会得到自动递增的号码,很可能不会超过300.如果他们需要手动ID,他们只能获得500到600之间的预定ID。)是的,我肯定是99.9999%不会有超过300个“自动识别”用户。

我真的希望能够提供我想要的任何手册ID,然后如果他们在线注册并且已经使用了ID,他们将获得下一个可用ID。我意识到这会破坏序列的目的,但是,我不确定我的另一个选择是什么。我只能提供一定数量的ID,所以我宁可不只是'max + 1',如果可能的话 - 我想“填补空白”。我很确定答案在于手动输入ID时更新序列,但这听起来不错,不确定原因。

如果这不是一个好主意,只要说 - “嘿,你是个白痴。谁让你负责数据库呢?”我们都会开始我们的一天。谢谢你的时间。

3 个答案:

答案 0 :(得分:3)

我可能完全DROP序列,ALTER TABLE从列中删除默认值。然后,在INSERT期间,我写了类似的内容:

BEGIN;
LOCK TABLE users IN EXCLUSIVE MODE;
INSERT INTO users (user_id, blah, blah)
VALUES (
   coalesce(
       requested_id_or_null_if_none_supplied,
       (SELECT coalesce(max(user_id),0) FROM users)+1
   ),
   'blah',
   'blah'
);
COMMIT;

这有很糟糕的并发性。正好一个事务可以在任何给定时刻插入用户。考虑到您正在使用的数量,只要您保持交易时间短,就应该完全没问题。

考虑添加一个用作公共显示标识符的新字段,与数据库的内部主键分开。使用此字段显示以满足客户的需要。让他们把他们想要的东西放进去,只需将它UNIQUE声明为合适的唯一密钥的一部分。

如果这样做,您可以使用相同的序列保留基础数字分配,因此您不必更改引用数据库中其他位置的用户的方式。您无需更改应用程序的其余部分在内部引用用户的方式。显示用户标识符时,只需查找“用户编号”进行显示并使用即可。当用户输入用户号码时,查找匹配行的主键。输入/输出只需要用户编号作为最终用户的标识符,不会在数据库的其余部分引用它。

这就是为什么数据库/应用程序设计智慧强调你通常不应该向用户公开生成的密钥。最终,如果他们能够看到他们,他们将不可避免地希望能够通过“有趣”的结果来改变它们。更容易给他们自己的公共标识符。如果他们突然决定它应该是字母数字,但只能在星期二满月 - 那没关系,你就可以做到。

将“显示标识符”和“唯一内部行键”拆分为单独的内容后,您可以随意使用显示标识符执行任何操作,包括轻松更改它。

答案 1 :(得分:2)

您显然已经开始讲授如何正确使用序列 。你在这个问题上表现出了一些见解,@ Craig的答案全面地弥补了这些差距。

关于你的问题,我会谈论反过来
为特殊客人预留一个特殊号码池。特殊数字应该简短 例如,10000(根据您的情况)开始您的序列。无需节俭,序列号便宜且丰富。

CREATE TABLE usr (
  usr_id serial PRIMARY KEY
 ,usr text   -- UNIQUE??
);

-- Let sequence start at 10000
SELECT setval('usr_usr_id_seq', 10000, FALSE);

-- Init table with first user if you want to start at certain number
-- Else numbers start at the lowest manual entry.
INSERT INTO usr(usr_id, usr) VALUES (1, 'first_user');

特别INSERT (如果请求的号码不可用,请取第一个免费号码):

WITH x AS (SELECT 4::int AS usr_id, 'special_guest' AS usr)
INSERT INTO usr(usr_id, usr)
SELECT CASE
         WHEN x.usr_id IS NULL THEN nextval('usr_usr_id_seq'::regclass)
         WHEN EXISTS (SELECT 1 FROM usr u WHERE u.usr_id = x.usr_id) THEN (
            SELECT u.usr_id + 1
            FROM   usr u
            WHERE  NOT EXISTS (SELECT 1 FROM usr u1
                               WHERE  u1.usr_id = u.usr_id + 1)
            ORDER  BY u.usr_id
            LIMIT  1)
         ELSE x.usr_id
       END
      ,x.usr
FROM   x;

通用INSERT (从序列中取数字):

INSERT INTO usr(usr) VALUES ('unspecial_guest');

- > sqlfiddle demo

并发

如果您需要担心并发性,那么您不应该首先使用它 此设置适用于特殊情况的手。

答案 2 :(得分:1)

我相信你的问题与序列无关。您可以创建一个产生负数的序列。这样您就可以完全分离手动和自动识别。但这并不能解决您的问题,因为您仍然需要找到积极ID中的“下一个可用数字”。

解决方案还取决于用户数量。如果您最多只有1000个用户。然后你可以用for循环确定“下一个更大的可用数字”。但是如果你有100万用户拥有随机(手动给定)标识符,那么问题就不那么容易了。

我不明白这里的概念。要么他们手动给出一个数字,要么他们没有。如果该号码已被占用,那么您为什么要找到最接近的号码?为什么它比序列返回的任何其他数字更好?我的意思是,如果已经使用了手动输入的号码,那么您可以向用户抛出错误消息。如果用户没有坚持给定的号码,那么任何其他号码都可以。正确?