一对“零或一”关系

Yos*_*nar 6 mysql database-design subtypes

我有以下要求:

  • 每笔交易都有以下类型之一;借记、贷记、存款或取款。

  • 借方或贷方交易必须有链接的发票记录,并且没有银行账户记录。

  • 存款或取款交易必须有关联的银行账户记录且无发票记录。


目前我的基本设计是这样的:

粗略的数据库设计

我目前的解决方案是:

  1. 一种交易表中发票和银行账户表的两个可为空的外键。但是,我认为可空的外键列不是一个好的设计。
  2. 在链接到交易表的发票和银行账户表中有一个外键。然而,它似乎更难查询,并且它模拟了一对多关系(不是我想要的一对一可选关系)。

我概述的哪种方法是更好的解决方案?或者还有其他更好的方法来满足我的约束吗?

joa*_*olo 2

尽管很多可为 NULL 的列通常是糟糕设计的标志,但在某些情况下,拥有它们并使用数据库CONSTRAINTS来确保保留 0-1 关系和业务规则是完全合法的。这将是您的解决方案#1。您的解决方案#2 不容易得到数据库的帮助,您最终可能会拥有一个事务,而在 和invoices表中都没有相应的行bank_accounts

\n\n

让我们暂时假设您没有使用MySQL 1(至少从版本 5.7 开始)。

\n\n

假设您正在使用另一个实际执行检查的数据库2,使用如下所示的模式是有意义的,其中包含一列invoice_id和一列,以及保证它们正确的bank_account_id必要约束REFERENCE正确表中的行(您称之为links),并CHECKS确保正确的行出现,不对应的行不存在:

\n\n
CREATE TYPE transaction_type AS ENUM\n    (\'debit\', \'credit\', \'deposit\', \'withdraw\') ;\n-- Note: this could be a table with four values (and probably four ids)    \n\nCREATE TABLE invoices\n(\n    invoice_id integer /* serial */ PRIMARY KEY,\n    other_data text\n) ;\n\nCREATE TABLE bank_accounts\n(\n    bank_account_id integer /* serial */ PRIMARY KEY,\n    name text,\n    other_data text\n) ;\n\nCREATE TABLE transactions\n(\n    transaction_id integer /* serial */ PRIMARY KEY, \n    type transaction_type,\n    nominal decimal(10, 2),\n    invoice_id integer REFERENCES invoices(invoice_id) ON UPDATE CASCADE ON DELETE RESTRICT,\n    bank_account_id integer REFERENCES bank_accounts(bank_account_id) ON UPDATE CASCADE ON DELETE RESTRICT,\n\n    -- Constraints for your business rules\n    CONSTRAINT chk_debit_and_credit_must_have_bank_account \n         CHECK (case when type in (\'debit\',\'credit\') then \n                    bank_account_id IS NOT NULL \n                else true end),\n    CONSTRAINT chk_debit_and_credit_must_not_have_invoice \n         CHECK(case when type in (\'debit\',\'credit\') then \n                   invoice_id IS  NULL \n                else true end),\n    CONSTRAINT chk_deposit_and_withdraw_must_have_invoice \n        CHECK(case when type in (\'deposit\',\'withdraw\') then \n                  invoice_id IS NOT NULL \n              else true end),\n    CONSTRAINT chk_deposit_and_withdraw_must_not_have_bank_account \n        CHECK(case when type in (\'deposit\',\'withdraw\') then \n                  bank_account_id IS NULL \n              else true end) \n) ;\n
Run Code Online (Sandbox Code Playgroud)\n\n

考虑到这一点,以及以下数据......

\n\n
-- Adding two invoices\nINSERT INTO invoices\n   (invoice_id, other_data)\nVALUES\n   (1, \'data for invoice 1\'),\n   (2, \'data for invoice 2\')\n;\n\n-- Adding two bank accounts\nINSERT INTO bank_accounts\n   (bank_account_id, other_data)\nVALUES\n   (1000, \'Bank account 1000\'),\n   (1001, \'Bank account 1001\')\n;\n
Run Code Online (Sandbox Code Playgroud)\n\n

...你可以拥有一个合法的INSERT

\n\n
-- Good credit and debit\nINSERT INTO transactions \n    (transaction_id, type, nominal, invoice_id, bank_account_id)\nVALUES\n    (2000, \'credit\', 1000.00, NULL, 1000),\n    (2001, \'debit\',   900.00, NULL, 1000) ;\n
Run Code Online (Sandbox Code Playgroud)\n\n

...以及一些非法的(被数据库拒绝)

\n\n
-- Bad credit, it\'s got invoice\nINSERT INTO transactions \n    (transaction_id, type, nominal, invoice_id, bank_account_id)\nVALUES\n    (2002, \'credit\', 1000.00, 1, 1000) ;\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n错误: 关系“交易”的新行违反了检查约束“chk_debit_and_credit_must_not_have_invoice”\n详细信息: 失败行包含 (2002, Credit, 1000.00, 1, 1000)。\n
\n\n
-- Bad credit, it\'s got not bank_account_id\nINSERT INTO transactions \n    (transaction_id, type, nominal, invoice_id, bank_account_id)\nVALUES\n    (2003, \'credit\', 1000.00, NULL, NULL) ;\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n错误: 关系“交易”的新行违反了检查约束“chk_debit_and_credit_must_have_bank_account”\n详细信息: 失败行包含 (2003, Credit, 1000.00, null, null)。\n
\n\n

(以及所有其他组合)

\n\n

仔细选择约束的名称对于调试错误的插入或更新有很大帮助。如果您需要最大速度,所有约束都可以减少到只有一个检查表达式。我通常会尝试让数据库帮助我,并保持简单(4 个简单的名称和 4 个易于阅读的表达式,而不是单个)。

\n\n

您可以在dbfiddle找到所有设置

\n\n
\n\n

1原因如下:

\n\n

不幸的是,MySQL 没有多大帮助,因为来自MySQL 5.7 手册中关于 CREATE TABLE 的内容

\n\n
\n

查看

\n\n

CHECK 子句被所有存储引擎解析但忽略。请参见第 1.8.2.3 节,\xe2\x80\x9c 外键差异\xe2\x80\x9d。

\n
\n\n

2我使用过 PostgreSQL。通过一些语法变化,这也适用于 SQL Server 或 Oracle

\n