【问题标题】:Identifiers in a one to many relationship一对多关系中的标识符
【发布时间】:2010-09-26 07:15:53
【问题描述】:

我有两个表,我们将它们称为Foo 和Bar,具有一对多关系,其中Foo 是Bar 的父级。 Foo 的主键是一个随序列自动生成的整数。

由于Bar 完全依赖于Foo,在以下约束条件下,我将如何设置Bar 的主键:

  • Bar 的记录是以编程方式进行的 生成所以用户输入不能 依赖于标识符。
  • 正在生成多个进程 酒吧记录所以任何涉及 Select Max() 生成 ID 将 提出竞争条件。

我提出了两种我不满意的可能解决方案:

  • 将表格视为 与一个多对多的关系 第三张表映射他们的记录 一起申请 代码处理插入记录所以 记录之间的映射 正确创建。我不喜欢 这是因为它使数据库设计 应用中的误导和错误 代码可能会导致无效数据。
  • 给 Bar 两个列:FooID 和 FooBarID 并为 FooBarID 通过选择 max(FooBarID)+1 对于一些 FooID,但是 如前所述,这创建了一个 比赛条件。

我很欣赏任何关于替代表格布局的想法。

【问题讨论】:

    标签: database oracle database-design ddl


    【解决方案1】:

    给 Bar 一个与 Foo 相同的自动主键。向 Bar 添加外键 FooID 列。

    除非我遗漏了一些东西,否则似乎没有理由让它不起作用。

    【讨论】:

      【解决方案2】:

      根据您的描述,我假设您的数据库不支持自动增量标识符字段(MS SQL 支持,Oracle 具有“序列”,即使不是更好,也一样好,我不记得 MySql 有)。

      如果有,那么你只需要一个自增的FooId和一个自增的BarId,Bar也有一个FooId作为外键

      如果没有,那么您可以创建一个单行表进行分配,如下所示:

      create table SystemCounter 
      ( 
          SystemCounterId int identity not null, 
          BarIdAllocator int 
      )
      --initialize SystemCounter to have one record with SystemCounterId = 1
      --and BarIdAllocator = 0
      insert into SystemCounter values (1,0)
      --id allocator procedure
      create procedure GetNextBarId ( @BarId int output ) AS
          SET NOCOUNT ON
          begin tran
              update SystemCounter set 
                  @BarId = BarIdAllocator = BarIdAllocator + 1
              where SystemCounterId = 1
          commit
      GO
      

      请注意,如果您的数据库不支持该语法

      @BarId = BarIdAllocator = BarIdAllocator + 1
      

      那么你需要这样做

      begin tran
          update SystemCounter set 
              BarIdAllocator = BarIdAllocator + 1
          where SystemCounterId = 1
          select 
              @BarId = BarIdAllocator
          from SystemCounter
          where SystemCounterId = 1
      commit
      

      编辑:我最初错过了 Oracle 标记,所以 Bill 的解决方案就是必要的。如果有人使用不支持身份或序列构造的数据库,我将这个答案作为一个示例来说明如何做到这一点

      【讨论】:

      • 您是否注意到 OP 将此问题标记为“oracle”? Oracle 不支持 IDENTITY,只支持 SEQUENCE。
      • @[Bill Karwin]:不,错过了! oracle 中的序列可用于实现自增身份
      【解决方案3】:

      除非我在您的描述中遗漏了某些内容,否则这听起来很普通。通常的解决方案是这样的:

      INSERT INTO Foo (foo_id, othercolumn)
        VALUES ( FooSeq.NextVal(), 'yadda yadda');
      
      INSERT INTO Bar (bar_id, foo_id, extracolumn)
        VALUES ( BarSeq.NextVal(), FooSeq.CurrVal(), 'blah blah');
      INSERT INTO Bar (bar_id, foo_id, extracolumn)
        VALUES ( BarSeq.NextVal(), FooSeq.CurrVal(), 'bling bling');
      INSERT INTO Bar (bar_id, foo_id, extracolumn)
        VALUES ( BarSeq.NextVal(), FooSeq.CurrVal(), 'baz baz');
      

      序列的CURRVAL() 函数仅返回该序列在当前会话期间生成的最新值。该序列的其他并发使用不会影响 CURRVAL() 在您的会话中返回的内容。

      【讨论】:

      • 由于我们专门讨论 Oracle,请注意嵌套的 VALUES 语法在 Oracle 中无效。您将需要 3 个单独的 INSERT ... VALUES 语句或选择 3 行的单个 INSERT SELECT(可能来自 DUAL)
      • 我认为更传统的做法是插入到 FOO 中,将 foo_id 返回到变量中,然后在后续插入中引用该变量。
      【解决方案4】:

      根据 Ant P 和其他答案,我也不太明白为什么只为 bar 生成唯一 ID 并将 Foo 的 ID 放入其中是行不通的。但是假设您处于自动递增 ID 不可用的情况,那么有两种解决方案不涉及选择 max(barid)+1

      1. 预先生成唯一 ID 表并使用事务从表中提取下一个可用 ID 并将其删除(作为原子操作)。这可以正常工作,但缺点是您必须保持表格填充。

      2. 生成一个 UUID 作为主键。这通常不是一个好的选择,因为 UUID 对于这种用途来说效率低下,但它确实具有不需要额外的基础结构表的优点。 UUID 生成器广泛可用,一些数据库内置了它们。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多