如何使用一个通用表建立关系

时间:2019-01-09 15:53:38

标签: mysql sql database foreign-keys primary-key

我现在拥有的表是经过“硬编码”的,以使同一张表中的两个表之间具有关系,而该表具有两个外键。

在“关系”表之前:

CREATE TABLE IF NOT EXISTS `Item_To_Item` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `item1_id` INT(11) NOT NULL,
  `item2_id` INT(11) NOT NULL,
  `relationship` ENUM('requires', 'mutually_requires', 'required_by', 'relates', 'excludes') NULL DEFAULT NULL,
  `description` VARCHAR(1000) NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  CONSTRAINT `fk_Item1_Id`
    FOREIGN KEY (`item1_id`)
    REFERENCES `Item` (`id`)
  CONSTRAINT `fk_Item2_Id`
    FOREIGN KEY (`item2_id`)
    REFERENCES `Item` (`id`)

因此,在此之前,项目表上有两个多对一的引用来填充两个外键。

现在需要扩展此关系,使其在数据库中的表之间更通用(枚举,标签,功能等)。因此,现在,项可以与项相关,项可以与标签相关,标签可以与枚举关系值相关的标签等。

我对通用表的想法是添加一个类型表,以便可以标识每个项目,标签等,然后将关系表重构为类似这样的东西:

CREATE TABLE IF NOT EXISTS `Relationship` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `relater_id` INT(11) NOT NULL,
  `relatee_id` INT(11) NOT NULL,
  `relationship` ENUM('requires', 'mutually_requires', 'required_by', 'relates', 'excludes') NULL DEFAULT NULL,
  `description` VARCHAR(1000) NULL DEFAULT NULL,
  `relater_type_id` INT(11) NULL,
  `relatee_type_id` INT(11) NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_Relatee_Id` (`relatee_id` ASC),
  INDEX `fk_Relater_Id` (`relater_id` ASC),
  CONSTRAINT `fk_Relater_Id`
    FOREIGN KEY (`relater_id`)
  CONSTRAINT `fk_Relatee_Id`
    FOREIGN KEY (`relatee_id`)

因此,您现在可以确定type_id和表关联的项目类型,并且可以将其打开,以便任何两个表id都可以进入“关联”和“关联”外键列。

问题是我不知道如何使用外键进行这种概括。我相信他们只能引用一个表,所以我不确定如何使用通用键引用来做我想做的事情。此外,我可以看到双向关系存在的问题,其中A相互要求B,B相互要求A是冗余数据。我可以在应用程序中阻止这种冗余,但是我将不得不不断检查A到B的两侧||。从B到A。我想知道完成我想做的最好的方法。谢谢。

编辑:也许对我的基本类型(商品,功能,标签)有帮助吗?

编辑:我不认为答案像继承那样简单。至少从我所能知道的。我的问题是,无论类型如何,我都想关联两个常规项目。我不希望20列必须为null,因为它不是特定类型。我只希望能够将两个id以及两个type_ids传递给关系,以便可以关联任何两个对象。

1 个答案:

答案 0 :(得分:1)

一种可能的解决方案是实现object_type和object_index表:

CREATE TABLE object_type (
  `object_type_id` int(11) NOT NULL AUTO_INCREMENT,
  `object_type` varchar(30) NOT NULL,
  PRIMARY KEY (`object_type_id`),
  UNIQUE (`object_type`));

CREATE TABLE object_index (
  `object_id` int(11) NOT NULL AUTO_INCREMENT,
  `object_type_id` int(11) NOT NULL,
  PRIMARY KEY (`object_id`),
  UNIQUE (`object_type_id`, `object_id`));

并仅定义与该表的关系。

CREATE TABLE IF NOT EXISTS `Relationship` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `relater_id` INT(11) NOT NULL,
  `relatee_id` INT(11) NOT NULL,
  `relationship` ENUM('requires', 'mutually_requires', 'required_by', 'relates', 'excludes') NULL DEFAULT NULL,
  `description` VARCHAR(1000) NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_Relatee_Id` (`relatee_id` ASC),
  INDEX `fk_Relater_Id` (`relater_id` ASC),
  CONSTRAINT `fk_Relater_Id`
    FOREIGN KEY (`relater_id`)
    references object_index (`object_id`),
  CONSTRAINT `fk_Relatee_Id`
    FOREIGN KEY (`relatee_id`)
    references object_index (`object_id`));

接下来,将定义每个对象表,使它们与唯一(object_type_id,object_id)元组上的object_index相关。在此示例中,每个表的默认值和检查约束的object_type_id应该是唯一的:

CREATE TABLE table1 (
  `object_id` int(11) NOT NULL,
  `object_type_id` int(11) NOT NULL DEFAULT 1 CHECK (object_type = 1),
  `col1` varchar(4),
  PRIMARY KEY (`object_id`),
  CONSTRAINT fk_t1_ob_idx
    FOREIGN KEY (`object_type_id`, `object_id`)
    REFERENCES object_index (`object_type_id`, `object_id`));

在MySQL 5.6及更高版本中,您可以在每个表上定义一个虚拟/计算列,以匹配来自object_index的object_type_id而不是存储的物理列。

在MySQL 8.0和更高版本中,您也许可以在每个表上定义一个基于函数的索引,该表将鉴别符object_type_id包含为表达式,而不是表中的物理列或虚拟列。