【问题标题】:Foreign key to multiple primary keys on different tables不同表上多个主键的外键
【发布时间】:2018-04-25 13:28:53
【问题描述】:

我有一个物业管理应用程序,其中内置了完整的会计系统。我有一个日记帐分录表,用于控制各种会计活动的所有过帐,例如:

发票
付款
账单
存款

在某些情况下,有必要将这些实体加入日记帐分录表,以按不同的属性和单位汇总会计分录。

我正在寻找最好的方法来做到这一点。我有几个选择:

1) 在日记帐分录表上添加一个外键以链接到 invoice_id、payment_id、bill_id、deposit_id,但是这些的大多数组合将是互斥的(即存款没有付款)所以我会有案例对于给定的日记条目,我将在那些不适用于该给定日记条目的外键中有空值。

2) 我可以创建一个外键,我们称之为 doc_id 和另一列 doc_type 来指示文档的类型(发票、付款、账单、存款等),并结合 doc_id 和 document_type_id 来引用其中一个扩展表上的主键(即 doc_id = 1 & doc_type = Invoice,该组合将引用 Invoice 表上的主键)。

哪种方法更好?还是我认为这一切都错了?

【问题讨论】:

  • 我不是数据库专家,几个月前才开始。要查看我是否理解您的问题,您有一个主表 (doc) 和其他表连接到它,并使用具有 1:n 关系的引用主键?
  • 是的,主表实际上是 journal_entry 表,Invoice、Payment、Deposit 和 Bill 表需要引用其中的相应条目,但是它们不会都有输入到的每条记录的值journal_entry 表(如选项 1 所示)。 1:n,是的。
  • 在我看来,每种方法都有其自身的问题。第一个选项表示在 journal_entry 中,每个表都有特定的列。因此,如果您需要添加具有相同引用类型的新表,则必须更改主表并为其添加一列。第二个选项将导致每个项目有很多行。每一行都表示要查找的位置。
  • 我会在主表和其他表之间建立一个连接表。一个有 3 列的表:journal_id、ref_id、ref_id_type。并将其用作连接管理器。这样如果你需要更新日志数据,你只需要更新1行(修复选项2的问题),如果你想添加新表,只需要在连接表中添加1行。
  • 也许我遗漏了一些东西,但是为什么不能在 Invoices、Payments 等中放置一个指向 Journal 表主键的外键?

标签: mysql database database-design


【解决方案1】:

这听起来像是标准的基础实体/子实体模式。有一个表,我们称之为 JournalEntries,其中包含所有日记条目的共同属性:ID、条目类型、创建时间、创建者等等。

create table JournalEntries(
    ID       Int  auto_generating primary key,
    EType    char( 1 )  not null check( EType in( 'I', 'P', 'B', 'D' )) -- Invoice, Payment, etc.
    Amount   currency  not null,
    CreateDate  Date not null,
    ...,     -- other common attributes
    constraint UQ_JournalEntryType unique( ID, EType ) -- create anchor for FKs
);

请注意,ID 是主键,因此是唯一的。因此,从域定义的角度来看,使 ID 和 EType 的组合唯一的约束是多余的。它所做的只是为外键定义一个锚点。

这些 FK 将位于子实体表中 - 每个子实体一个表:发票、付款、账单和存款。请注意,如果在 JournalEntries 表中将条目定义为存款 (EType = 'D'),则只能在存款表中创建相应的条目。例如,您不能在 Payments 表中错误地使用该 ID。

让我们定义一个子实体表:

create table Invoices(
    ID    int   primary key, -- value generated by JournalEntries table
    IType char( 1 ) not null check( IType = 'I' ), -- Nothing but invoices
    ...,   -- Invoice-specific attributes
    constraint FK_InvoiceToEntry foreign key( ID, IType )
      references JournalEntries( ID, EType )
);

现在让我们创建一个始终与一个 Invoice 相关联并且可能有任意数量的其他条目的活动。约束确保只能插入发票,并且 ID 值必须与定义为发票的 JournalEntries 条目匹配。

create table Activities(
  ID   int   auto_generating primary key,
  InvID int  not null,
  IType char( 1 ) check( IType = 'I' ),
  ...,   -- other data
  constraint FK_ActivityInvoice foreign key( InvID, Type )
);

可能有任意数量的附加条目,它们可能是任何条目类型,因此您需要一个交集表:

create table ActivityEntries(
    ActID  int  not null,
    EntID  int  not null,
    DateEntered date not null,
    constraint FK_ActEntry_Activity foreign key( ActID )
      references Activities( ID ),
    constraint FK_ActEntry_JEntry foreign key( EntID )
      references JournalEntries( ID )
);

请注意,“Journal Entry”是与来自其中一个子实体表的关联数据连接的 JournalEntries 数据。因此,对任何日记帐分录的 FK 引用都应引用 JournalEntries 表,而不是任何子实体表,即使您知道它是哪种分录。因此,活动行使用 EType 字段引用 JournalEntries 表作为额外的数据完整性工作,因为它必须是发票。交集表包含任何类型的条目,因此其 FK 目标只是 PK。

注意:出于说明目的,JournalEntries 表中的类型指示符受检查语句的约束。在实际数据库中,更好的设计是条目类型查找表。这保持了数据完整性,但设计更加灵活。 (加上 MySQL 仍然(!)没有实现检查约束的事实。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-16
    • 1970-01-01
    • 1970-01-01
    • 2021-09-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多