2

好的,我正在使用 PostgresQL 制作用户数据库,我已经使用了一两个月,但不是很熟悉。目前,每个注册的用户都会通过 PostgresQL 序列自动分配一个唯一的 ID。这一直是惯例,并且一直运行良好,但是......我有来自客户的请求,希望能够手动输入用户的某些 ID。我预计用户不会超过 300 个,因此我为手动输入的 ID 保留了 ID 500-600。(如果他们在线注册,他们会得到自动递增的数字,很可能不会超过 300。如果他们需要手动 ID,他们会获得一个预先确定的 ID,介于 500 和 600 之间。)是的,我 99.9999% 确信不会有超过 300 个“自动识别”用户。

我真的希望能够提供我想要的任何手动 ID,然后如果他们在线注册并且 ID 已被占用,他们将获得下一个可用 ID。我意识到这违背了序列的目的,但是,我不确定我的其他选择是什么。我只能给出一定数量的 ID,所以如果可能的话,我宁愿不只是“max+1”——我想“填补空白”。我相当确定答案在于手动输入 ID 时更新序列的某个地方,但这听起来是个坏主意,不知道为什么。

如果这不是一个好主意,就说——“嘿,你是个白痴。谁让你负责数据库的?” 我们将继续我们的一天。谢谢你的时间。

4

3 回答 3

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为合适的唯一键的一部分。

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

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

一旦您将“显示标识符”和“唯一内部行键”拆分为单独的东西,您就可以自由地对显示标识符做任何您想做的事情,包括轻松地让他们更改它。

于 2012-10-03T23:27:25.093 回答
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');

特殊插入(如果请求的号码不可用,则取第一个免费号码):

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 演示

并发

如果您需要担心并发性,则不应一开始就使用它。
此设置适用于充满特殊情况的手。

于 2012-10-04T01:14:58.300 回答
1

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

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

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

于 2012-10-03T17:04:46.767 回答