如何在PGSQL中为密钥创建唯一的自动递增修订版号?

时间:2009-05-26 18:31:59

标签: sql postgresql sequence constraints

假设我有以下表格。

PARENT: PARENT_ID serial, DESCRIPTION character varying(50)

CHILD: PARENT_ID integer, CHILD_ID integer, DESCRIPTION character varying(50)

我希望看到CHILD中的每一行都有一个CHILD_ID,从1开始增加1,每个PARENT_ID唯一。它类似于修订号。例如..

PARENT_ID 1, CHILD_ID 1
PARENT_ID 1, CHILD_ID 2
PARENT_ID 1, CHILD_ID 3
PARENT_ID 2, CHILD_ID 1
PARENT_ID 3, CHILD_ID 1
PARENT_ID 3, CHILD_ID 2

是否有任何方法可以自动分配CHILD_ID值,例如序列或约束,只能重用已删除的CHILD_ID?我能弄清楚的唯一方法是这个SQL的效果。

INSERT INTO child SELECT parent_id, MAX(child_id)+1, 'description' FROM child WHERE parent_id = :PARENT_ID GROUP BY parent_id

但这有点像黑客。我意识到数据库规范化表明你不应该有一个与另一个密钥相关的密钥,但由于某些其他原因我没有这个选项。有什么想法吗?

编辑:标题很难看。如果你们中任何一个高分的人都能想到一个更准确的人,请随时改变它。

3 个答案:

答案 0 :(得分:3)

我建议使用:

CHILD: PARENT_ID integer, CHILD_ID serial, DESCRIPTION character varying(50)

当您需要获得所需结果时:

  • 您可以在客户端计算行数。

  • 选择PARENT_ID =?的行?你可以使用临时序列。

  • 在即将发布的Postgresql 8.4中,你可以使用这样的窗口函数:

    $ create table child (parent_id integer, child_id serial);
    NOTICE:  CREATE TABLE will create implicit sequence "child_child_id_seq" for serial column "child.child_id"
    CREATE TABLE
    
    $ insert into child (parent_id) values (1), (1), (1), (2), (3), (3);
    
    $ select * from child;
     parent_id | child_id 
    -----------+----------
             1 |        1
             1 |        2
             1 |        3
             2 |        4
             3 |        5
             3 |        6
    (6 rows)
    
    $ select parent_id, row_number() over (partition by parent_id order by child_id) from child;
     parent_id | row_number 
    -----------+------
             1 |          1
             1 |          2
             1 |          3
             2 |          1
             3 |          1
             3 |          2
    (6 rows)
    

它非常快速,易于实现,并且可以很好地扩展,因为不会担心并发问题。

答案 1 :(得分:0)

虽然插入不是全部故事。如果你真的希望数字是连续的,你还需要处理删除以缩小创建的差距。

我的建议是根据需要推导出这个值。是什么决定了这个数字的顺序?如果它是输入系统的日期,那么将该日期添加到您的表中并将您的PK放在parent_id和该日期上,那么您可以很容易地通过SQL或前端根据需要提供该数字。< / p>

答案 2 :(得分:0)

您可以在父表上使用递增版本号,并将子ID设置为该值并递增它。您可能需要更新父行并在单个事务中插入子行。

BEGIN
-- Get and hold onto parent_id and version values.
SELECT PARENT_ID, VERSION FROM PARENT WHERE PARENT_ID = :PARENT_ID;
-- Use the values to insert into the child table
INSERT INTO CHILD (PARENT_ID, CHILD_ID) VALUES (:PARENT_ID, :VERSION);
-- Update the version using an optimistic lock.
UPDATE PARENT SET VERSION = VERSION + 1 WHERE PARENT_ID = :PARENT_ID AND 
                                              VERSION = :VERSION_ID
-- If no rows are updated rollback the transaction and try again.
END

这将确保子ID严格提升,但在删除后不会重用id值。如果您可以避免重用旧ID的限制,它将简化您的解决方案(并且解决方案将更有效)。如果您必须重复使用ID,那么您有2个选项,首先是您在上面指定的解决方案,但删除后重新编号在您删除的值之后发生的所有值。另一种选择是使用某种函数按顺序扫描子ID并将它们与一组序列号进行比较,并在找不到第一个时返回值。这两个解决方案都比较复杂,并且速度很慢,因为您需要取出行锁以防止并发更新,插入或插入和删除都会导致O(n)惩罚。