2

假设我有以下表格。

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

不过,这有点骇人听闻。我意识到数据库规范化表明您不应该将一个密钥与另一个密钥相关联,但由于其他一些原因,我没有该选项。有任何想法吗?

编辑:标题很难看。如果有高分的人能想到一个更准确的,请随时更改。

4

3 回答 3

4

我建议使用:

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)
    

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

于 2009-05-27T08:35:02.383 回答
0

不过,该插页并不是故事的全部。如果您真的希望数字是连续的,您还需要处理删除以缩小创建的差距。

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

于 2009-05-26T19:46:45.257 回答
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) 损失。

于 2009-05-26T20:24:20.377 回答