基于附加列强制执行外键约束

Posted

技术标签:

【中文标题】基于附加列强制执行外键约束【英文标题】:Enforcing foreign key constraints based on additional column 【发布时间】:2019-01-09 01:56:11 【问题描述】:

我有以下业务规则:

    一个帐户可以有零个或多个许可证。 每个许可证都有一个唯一的 guid。 每个许可证都预先分配给给定帐户。 所有安装都属于给定帐户。 所有商业安装都需要许可证。 任何两个商业安装都不能使用同一个许可证。 可以删除商业安装,然后可以将许可证用于新的商业安装。 commercial_installation 只能使用已分配给安装帐户的许可证。

我如何执行最后一条规则? 商业安装只能使用已分配给安装帐户的许可证。 或者换句话说,对于给定的 guid要存储在commercial_installations 中,licenses.accounts_id 必须等于installations.accounts_id。请参阅底部的示例数据。

CREATE TABLE IF NOT EXISTS accounts (
  id INT NOT NULL AUTO_INCREMENT,
  name VARCHAR(45) NOT NULL,
  otherData VARCHAR(45) NULL,
  PRIMARY KEY (id))
ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS licenses (
  guid CHAR(36) NOT NULL,
  accounts_id INT NOT NULL,
  otherData VARCHAR(45) NULL,
  PRIMARY KEY (guid),
  INDEX fk_licenses_accounts1_idx (accounts_id ASC),
  CONSTRAINT fk_licenses_accounts1
    FOREIGN KEY (accounts_id)
    REFERENCES accounts (id)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS installations (
  id INT NOT NULL AUTO_INCREMENT,
  accounts_id INT NOT NULL,
  otherData VARCHAR(45) NULL,
  PRIMARY KEY (id),
  INDEX fk_installations_accounts1_idx (accounts_id ASC),
  CONSTRAINT fk_installations_accounts1
    FOREIGN KEY (accounts_id)
    REFERENCES accounts (id)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS open_installations (
  installations_id INT NOT NULL,
  otherData VARCHAR(45) NULL,
  PRIMARY KEY (installations_id),
  CONSTRAINT fk_open_installations_installations1
    FOREIGN KEY (installations_id)
    REFERENCES installations (id)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS commercial_installations (
  installations_id INT NOT NULL,
  licenses_guid CHAR(36) NOT NULL,
  otherData VARCHAR(45) NULL,
  PRIMARY KEY (installations_id),
  UNIQUE INDEX fk_commercial_installations_licenses1_idx (licenses_guid ASC),
  CONSTRAINT fk_commercial_installations_installations1
    FOREIGN KEY (installations_id)
    REFERENCES installations (id)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT fk_commercial_installations_licenses1
    FOREIGN KEY (licenses_guid)
    REFERENCES licenses (guid)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

给定以下示例数据:

b5060518-f87e-4acc-82c8-adb5750685a9d6f23460-0d77-400e-ae96-13f436e40245 只能存在于commercial_installations 中,其中commercial_installations.id12。 同样,8739ef62-7fff-4913-81de-3d00e8f50ecb 对应于 3。 同样,36cc0787-5cb9-4c3a-b79d-1dcfb83d2794 对应于 4

帐户

+----+-----------+
| id |   name    |
+----+-----------+
|  1 | Account 1 |
|  2 | Account 2 |
|  3 | Account 3 |
|  4 | Account 4 |
+----+-----------+

许可证

+--------------------------------------+-------------+
|                 guid                 | accounts_id |
+--------------------------------------+-------------+
| b5060518-f87e-4acc-82c8-adb5750685a9 |           1 |
| d6f23460-0d77-400e-ae96-13f436e40245 |           1 |
| 36cc0787-5cb9-4c3a-b79d-1dcfb83d2794 |           2 |
| 8739ef62-7fff-4913-81de-3d00e8f50ecb |           3 |
+--------------------------------------+-------------+

安装

+----+-------------+
| id | accounts_id |
+----+-------------+
|  1 |           1 |
|  2 |           1 |
|  3 |           3 |
|  4 |           2 |
+----+-------------+

【问题讨论】:

@RaymondNijland 我目前这样做,但仅限guid。我可以创建licenses 主键组合guid-accounts_id 或添加第二个约束commercial_installations.accounts_id 引用accounts.id。但是如果我这样做了,则不会强制执行 commercial_installations.accounts_idinstallations.accounts_id 相同,并且我不能添加这样的约束,因为 installations.accounts_id 已经引用了 acccounts.id。我绕了一圈又一圈…… "但是如果我这样做了,commercial_installations.accounts_idinstallations.accounts_id 相同,我无法添加这样的约束,因为 installations.accounts_id 已经引用了 acccounts.id。周围和周围我去“真的,现在我看起来更好了......我认为你需要使用 BEFORE INSERT 触发器来检查 commercial_installations.accounts_id 是否与 installations.accounts_id 相同,方法是选择许可证和帐户。你能添加一些表的示例记录? @RaymondNijland 我添加了示例数据。我在想一个触发器。我犹豫不决,因为每当我这样做时,我都需要让它抛出一些自定义异常,无论我记录得多么好,我都会忘记它并让自己有很大的机会。如果是触发器,您建议如何实施?谢谢 在深入了解您的数据模型之后。我还想知道为什么有一个看起来不需要的commercial_installations 表。除了“商业”对我来说听起来更像是一种元数据(类型)。使用元数据作为表名或多或少是一种 SQL 反模式。我将删除 commercial_installations 表并将 installation_id 字段添加到许可证表中,也无需存储两次 guid。除非表 @987654356 @ 表示从表 installations 到坏 mysql 的继承不像 PostgreSQL 那样支持继承 @RaymondNijland - “在我看来,除了“商业”之外,它更像是一种元数据(类型)。使用元数据作为表名或多或少是一种 SQL 反模式。” hmmm ,在多个子表之间有一个具有外键弧的建模子类型的完善约定,在我看来,安装类型的实现符合这种模式。这必须比具有复杂检查约束的单个表更好,这些约束针对不同类型的安装执行规则。正如你确实承认你提到了继承 【参考方案1】:

当然,您可以在应用程序的逻辑中强制执行它,但我想您想在数据库级别执行它。

我在这里看到的唯一选项是为INSERTUPDATEDELETE 操作添加触发器。普通外键不会为您执行此操作。

我喜欢这个问题,因为我之前也遇到过这个问题,并且没有真正花时间在 SO 中正式询问它。

【讨论】:

我目前正在应用程序中执行,但是是的,我宁愿在数据库级别执行此操作。谢谢【参考方案2】:

只需将account_ID 传播到commercial_installations。它还需要更多的唯一约束,标记为 SK(超键,键的超集),以用作模型中 FK 的目标。

帐户 account_ID, account_name PK account_ID 许可证 license_ID, account_ID PK license_ID SK license_ID, account_ID FK account_ID 参考帐户 account_ID 安装 installation_ID、account_ID、installation_TYPE PK installation_ID SK installation_ID, account_ID FK account_ID 参考帐户 account_ID open_installations installation_ID PK installation_ID FK installation_ID 参考安装 installation_ID Commercial_installations installation_ID、account_ID、license_ID PK installation_ID AK license_ID FK1 installation_ID, account_ID 参考安装 installation_ID, account_ID FK2 license_ID, account_ID REFERENCES 许可证 license_ID, account_ID 笔记: 所有属性(列)NOT NULL PK = 主键 SK = Superkey(唯一) AK = 备用键(唯一) FK = 外键

【讨论】:

以上是关于基于附加列强制执行外键约束的主要内容,如果未能解决你的问题,请参考以下文章

Netezza 外键约束

Sql Server 主键 外键约束

如何在连接表上的两个外键之间强制实施数据库约束?

Oracle复习约束以及视图

主键,外键

主外键