【问题标题】:Two foreign keys, one of them not NULL: How to solve this in SQL?两个外键,其中一个不为 NULL:如何在 SQL 中解决这个问题?
【发布时间】:2017-07-27 11:31:39
【问题描述】:

我有一张桌子time。时间条目(1:n 关系)要么属于 project 条目,要么属于 special_work 条目。必须设置项目 id 或 special_work id,两者都不能(独占或)。

CREATE TABLE `time` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `project` int(20) NOT NULL,
  `special_work` int(20) NOT NULL,
  `date` date NOT NULL,
  `hours` float NOT NULL,
  `time_from` time DEFAULT NULL,
  `time_to` time DEFAULT NULL,
  `notes` text NOT NULL,
  PRIMARY KEY (`id`),
  FOREIGN KEY (`project`) REFERENCES `project`(`id`)
  FOREIGN KEY (`special_work`) REFERENCES `special_work`(`id`)
) DEFAULT CHARSET=utf8;

如何在 SQL 中编写此代码?除了触发器还有什么办法?

如果您确定这是糟糕的数据库设计 - 有没有更好的建模方法?但是我确实想要两个不同的时间表

我的数据库是 Mysql 5.5 InnoDB。

【问题讨论】:

  • 您想要避免触发的原因是什么?如果只是出于好奇不用担心,如果是因为你被告知“触发器不好”,那有点不正确。有时触发器很糟糕;有时它们是可以接受的,具体取决于场景。这是可接受的场景之一。
  • MySQL 的 FK 很糟糕。即使使用不太蹩脚的 FK 实现,也无法解决所有问题

标签: mysql sql database-design innodb mysql-5.5


【解决方案1】:

您的数据模型很好。在大多数数据库中,您还可以添加 check 约束:

alter table `time` add constraint chk_time_project_special_work
    check (project is not null xor special_work is null);

但是,MySQL 不支持检查约束。如果你真的喜欢,你可以使用触发器来实现逻辑。

【讨论】:

  • 谢谢。开始讨厌 MySQL,但所有那些提供共享主机的廉价虚拟主机都不支持其他任何东西。 Postgres 和 MariaDB 都支持它。
  • 虚假经济。从长远来看,MySQL 可能会花费你更多。您必须为此创建触发器这一事实太荒谬了。
  • 嗨,我认为从规范化的角度来看这是不正确的。你有这么多空外键(当数据库逐渐增长时)。如果我们在一张表中有 4 或 5 个互斥外键怎么办?
【解决方案2】:

像这样的互斥引用可能表明您的设计中缺少“超类型”表。考虑创建一个代表所有“工作”的新超类型表——包括项目和特殊工作。项目和特殊工作表将一对一地引用超类型表,该表将包含所有项目/工作标识符的联合。然后,时间表只需要一个引用超类型的不可为空的外键。

【讨论】:

  • 好主意。就我而言,这会使事情变得复杂,因为 special_work 有 90% 不同的列以及对项目的可为空引用。
  • 我认为这会简化事情。将 90% 的“不同”属性保留在 special_work 表中。将 10% 的共享属性移动到新的超类型表中。这里的工作原理是正交设计。您应该尽量避免在多个表中使用相同的属性。
  • 按照这些思路,如果special work 添加到project 并且project 没有任何额外内容,只需在project 上创建一个指向special work 的外键,然后从@ 中删除所有字段987654326@ 已经在project 中。这样,如果project.specialWorkId 为空,它就是一个项目;如果它有一个值,则会为您提供special work 的额外字段。
【解决方案3】:

设置

考虑 3 张桌子

  • 人类

还有一张桌子

  • 考试

检查可能是人的 xor 的猫 xor 的狗。

我认为以下三种变体都是可能的:

变体 1(将其放在一个表中)

考试

  • 身份证
  • ...
  • human_id
  • cat_id
  • dog_id

  • 检查约束/触发器这三个值中只有一个可以为空

变体 2(创建关系表)

humans_examinations

  • 身份证
  • human_id
  • examination_id

cats_examinations

  • 身份证
  • cat_id
  • examination_id

dogs_examinations

  • 身份证
  • dog_id
  • examination_id

变体 3(创建超类型)

患者

  • 身份证
  • ...(可能是人类/猫/狗的其他常见领域)

人类

  • 身份证
  • ...
  • 患者 ID

  • 身份证
  • ...
  • 患者 ID

  • 身份证
  • ...
  • 患者 ID

考试

  • 身份证
  • ...
  • 患者 ID

【讨论】:

    【解决方案4】:

    @GordonLinoff's answer;在 MySql 中,触发器是实现此约束的最佳方式。您是否有不想使用触发器的具体原因?

    由于您明确要求提供触发器以外的选项,因此这里有一些替代选项:

    存储过程

    使用存储过程插入此表/在此处包含验证逻辑。

    这确保通过存储过程插入/更新的任何内容都受到保护;但存在的问题是,有人直接将数据插入表中,可能会绕过此验证。

    可以通过使用安全性确保只有存储过程有权插入此表/更新这些字段来缓解该问题。有关设置此安全性的更多详细信息,请参阅此答案:https://stackoverflow.com/a/37536428/361842

    客户代码

    类似于存储过程选项;如果您拥有将执行这些插入/更新的所有代码,则可以在数据库之外添加验证。同样,您的验证可以被绕过(即直接进入数据库),但如果用户有权访问您的数据库,这只是一个问题。如果您的应用程序是在数据库中操作数据的唯一方法,这就足够了。存储过程选项更可取,但如果出于某种原因它不可行,这是您的下一个最佳选择。

    异步完整性检查

    最后一个选项是让一些工作定期运行数据完整性检查并报告问题。这不会防止数据出错,但会帮助您快速意识到它,以便您可以调查和解决它。一般来说,我会避免这种情况,因为防止坏数据比以后清理要好得多;但在某些用例中,这是唯一的选择。

    修改数据模型

    @SqlVogel's answer(建议换型号)也不错;即projectspecial case 之间有什么区别/关系?它们可以被建模为相同的东西吗?仅适用于其中一个和/或另一个的附加属性。

    【讨论】:

      猜你喜欢
      • 2017-05-07
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 2018-11-10
      • 2016-01-24
      • 2011-09-19
      • 2020-09-27
      • 1970-01-01
      相关资源
      最近更新 更多