简体   繁体   English

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

[英]Update sequence to next available in PostgresQL

OK, I'm making user database in PostgresQL, which I have been using for a month or two, but am not very familiar with. 好的,我正在使用PostgresQL创建用户数据库,该数据库已经使用了一个月或两个月,但并不十分熟悉。 Currently, each user that registers is auto-assigned a unique ID via a PostgresQL sequence. 当前,每个注册用户都是通过PostgresQL序列自动分配的唯一ID。 This has always been the practice and has always worked fine, HOWEVER... I have a request from the client to be able to manually enter users certain IDs. 这一直是惯例,并且一直运作良好,但是……我有一个来自客户端的请求,要求能够手动输入用户某些ID。 I'm not expecting more than 300 users, so I was reserving IDs 500-600 for manually-entered IDs. 我预计不会有300个以上的用户,因此我为手动输入的ID保留了ID 500-600。 (If they register online, they get the auto-incremented number, most likely not over 300. If they need a manual ID, they are given a pre-determined ID between 500 and 600 only.) Yes, I am 99.9999% sure that there will not be more than 300 'auto-id' users. (如果他们在线注册,他们会获得自动递增的数字,最多可能不会超过300。如果需要手动ID,则会为他们指定一个介于500到600之间的预定ID。)是,我对99.9999% “自动识别”用户将不超过300。

I would really like to be able to give any manual ID I'd like and then if they register online and the ID is already taken, they get the next available ID. 我真的很想能够提供我想要的任何手动ID,然后如果他们在线注册并且该ID已被使用,他们将获得下一个可用的ID。 I realize this defeats the purpose of the sequence, however, I'm not sure what my other option is. 我意识到这违反了序列的目的,但是,我不确定我的其他选择是什么。 I only have a certain number of IDs I can give out, so I'd rather not just 'max+1', if possible - I'd like to "fill in the gaps". 我只能提供一定数量的ID,因此,如果可能的话,我宁愿不只是'max + 1'-我想“填补空白”。 I'm fairly sure the answer lies somewhere in updating the sequence when an ID is manually entered, but that sounds like a bad idea, not sure why. 我相当确定答案是在手动输入ID时更新序列中的某处,但这听起来是个坏主意,不确定原因。

If it's not a great idea, just say - "Hey, you're a moron. Who put you in charge of a database?" 如果不是一个好主意,只需说-“嘿,您是个白痴。谁让您负责数据库?” and we'll all go about our day. 然后我们会全力以赴。 Thanks for your time. 谢谢你的时间。

I'd probably DROP the sequence completely and ALTER TABLE to remove the default from the column. 我可能会DROP完全顺序和ALTER TABLE删除从列的默认。 Then, during INSERT , I'd write something like: 然后,在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;

This has bad concurrency. 这具有不好的并发性。 Exactly one transaction can be inserting a user at any given moment. 在任何给定时刻,恰好一项事务可以插入用户。 Given the volumes you're working with that should be perfectly fine so long as you keep your transactions short. 鉴于您正在处理的交易量,只要您保持交易量短就可以了。

Consider adding a new field that's used as a public display identifier , separate to the database's internal primary key . 考虑添加一个新字段,用作数据库的内部主键之外的公共显示标识符 Use this field for display for the customer's purposes. 使用此字段进行显示以供客户使用。 Let them put whatever they want in it, just declare it UNIQUE as part of a suitable unique key. 让他们将所需的内容放入其中,只需将其声明为UNIQUE的适当唯一键的一部分即可。

If you do that you can keep the underlying number assignment using a sequence the same, so you don't have to change how you refer to the users elsewhere in the DB. 如果这样做,则可以使用相同的序列来保持基础编号分配,因此不必更改在数据库中其他地方引用用户的方式。 You don't have to change how any of the rest of the app refers to users internally. 您无需更改应用程序其余部分在内部对用户的引用方式。 When displaying a user identifier, just look up the "user number" for display and use that. 显示用户标识符时,只需查找“用户号”以进行显示并使用它。 When the user enters a user number, look up the primary key of the matching row. 当用户输入用户号码时,查找匹配行的主键。 The user number is only needed for input/output as an identifier for the end user, it doesn't get referenced in the rest of the database. 用户号仅在输入/输出时才需要作为最终用户的标识符,而在其余数据库中则不会被引用。

This is why database/app design wisdom stresses that you should generally not expose generated keys to the user . 这就是为什么数据库/应用程序设计常识强调您通常不应将生成的密钥公开给用户 Eventually, if they can see them they'll inevitably want to be able to change them with "fun" results. 最终,如果他们能够看到它们,他们将不可避免地希望能够通过“有趣”的结果来更改它们。 Much easier to give them their own public identifier to play with. 给他们提供自己的公共标识符要容易得多。 If they suddenly decide it should be alphanumeric, but only on Tuesdays with a full moon - that's OK, you can do that. 如果他们突然决定应该使用字母数字,但只能在满月的星期二进行-可以,您可以这样做。

Once you've split the "display identifier" and "unique internal row key" into separate things, you're free to do whatever you want with the display identifier, including easily letting them change it. 将“显示标识符”和“唯一内部行键”拆分为单独的内容后,您就可以随意使用显示标识符进行任何操作,包括轻松地让他们进行更改。

You obviously got your lecture on how to use a sequence properly . 显然,您已获得有关如何正确使用序列的讲座。 You show some insight in the question and @Craig's answer covers the gaps comprehensively. 您会在问题中显示一些见识,@ Craig的答案将全面弥补这些空白。

As to your question, I would go about it the other way round : 至于你的问题,我会去了解它的其他方式轮
Reserve a pool of special numbers for special guests. 为特殊客人预留特殊号码池。 Special numbers should be short. 特殊数字应简短。
Start your sequence at, say, 10000 (for your case). 10000 (对于您的情况)。 No need to be thrifty, sequence numbers are cheap and abundant. 无需节俭,序列号便宜且丰富。

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');

Special INSERT (take first free number, if requested number not available): 特殊的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;

Generic INSERT (take number from sequence): 通用INSERT (从序列中获取编号):

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

-> sqlfiddle demo -> sqlfiddle演示

Concurrency 并发

If you need to worry about concurrency, you shouldn't be using this to begin with. 如果您需要担心并发性,则不应该一开始就使用它。
This setup is for a hand full of special cases. 此设置适用于特殊情况。

I believe your problem has nothing to do with sequences. 我相信您的问题与序列无关。 You can create a sequence that yields negative numbers. 您可以创建一个产生负数的序列。 That way you can totally separate manual and auto identifers. 这样,您就可以完全分离手动和自动识别符。 But this doesn't solve your problem because you would still have to find the "next available number" among positive ids. 但这并不能解决您的问题,因为您仍然必须在正ID中找到“下一个可用号码”。

The solution also depends on the number of users. 解决方案还取决于用户数量。 If you will only have 1000 users max. 如果您最多只有1000个用户。 then you can determine the "next bigger available number" with a for loop. 那么您可以使用for循环确定“下一个更大的可用数字”。 But if you are going to have 1 million users with random (manually given) identifiers, then the problem is not that easy. 但是,如果您要拥有一百万个具有随机(手动给定)标识符的用户,那么问题就不那么容易了。

I don't understand the concept here. 我在这里不明白这个概念。 Either they give a number manually, or they do not. 他们要么手动给一个号码,要么不给。 If the number is already occupied, then why do you want to find the closest that is available? 如果号码已被占用,那么为什么要查找最接近的号码? Why it is better then any another number returned by a sequence? 为什么比序列返回的任何其他数字都要好? I mean, if the manually entered number is already used, then you could just throw up an error message to the user. 我的意思是,如果手动输入的号码已被使用,那么您可能会向用户抛出错误消息。 If the user does not insist on the given number, then any other number will do. 如果用户不坚持给定的号码,那么其他任何号码都可以。 Right? 对?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM