可以为两个可能的表之一做一个MySQL外键吗?

And*_*son 169 mysql polymorphic-associations

那么这是我的问题我有三张桌子; 地区,国家,国家.国家可以在区域内,州可以在区域内.地区是食物链的顶端.

现在我添加一个包含两列的popular_areas表; region_id和popular_place_id.是否有可能使popular_place_id成为国家州的外键.我可能不得不添加一个popular_place_type列来确定id是否描述了一个国家或州.

Bil*_*win 257

您所描述的内容称为多态关联.也就是说,"外键"列包含必须存在于一组目标表之一中的id值.通常,目标表以某种方式相关,例如是一些常见的超类数据的实例.您还需要外键列旁边的另一列,以便在每一行上,您可以指定引用哪个目标表.

CREATE TABLE popular_places (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  place_type VARCHAR(10) -- either 'states' or 'countries'
  -- foreign key is not possible
);
Run Code Online (Sandbox Code Playgroud)

没有办法使用SQL约束来建模多态关联.外键约束始终引用一个目标表.

Rails和Hibernate等框架支持多态关联.但他们明确表示必须禁用SQL约束才能使用此功能.相反,应用程序或框架必须执行相同的工作以确保满足引用.也就是说,外键中的值存在于一个可能的目标表中.

多态关联在强制数据库一致性方面很弱.数据完整性取决于所有使用相同参照完整性逻辑访问数据库的客户端,并且强制执行必须没有错误.

以下是一些利用数据库强制参照完整性的替代解决方案:

为每个目标创建一个额外的表.例如popular_statespopular_countries,分别参考statescountries.这些"流行"表中的每一个也引用用户的配置文件.

CREATE TABLE popular_states (
  state_id INT NOT NULL,
  user_id  INT NOT NULL,
  PRIMARY KEY(state_id, user_id),
  FOREIGN KEY (state_id) REFERENCES states(state_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);

CREATE TABLE popular_countries (
  country_id INT NOT NULL,
  user_id    INT NOT NULL,
  PRIMARY KEY(country_id, user_id),
  FOREIGN KEY (country_id) REFERENCES countries(country_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);
Run Code Online (Sandbox Code Playgroud)

这意味着要获得所有用户最喜欢的地方,您需要查询这两个表.但这意味着您可以依靠数据库来强制实现一致性.

创建一个places表作为超级表. 由于艾比提到,第二个选择是你的热闹的地方引用一个表像places,这是父母双方statescountries.也就是说,两个州和国家也有外键places(你甚至可以使这个外键也是主键statescountries).

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  PRIMARY KEY (user_id, place_id),
  FOREIGN KEY (place_id) REFERENCES places(place_id)
);

CREATE TABLE states (
  state_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (state_id) REFERENCES places(place_id)
);

CREATE TABLE countries (
  country_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);
Run Code Online (Sandbox Code Playgroud)

使用两列. 可以使用两列,而不是可以引用两个目标表中的任何一列.这两列可能是NULL; 事实上,其中只有一个应该是非NULL.

CREATE TABLE popular_areas (
  place_id SERIAL PRIMARY KEY,
  user_id INT NOT NULL,
  state_id INT,
  country_id INT,
  CONSTRAINT UNIQUE (user_id, state_id, country_id), -- UNIQUE permits NULLs
  CONSTRAINT CHECK (state_id IS NOT NULL OR country_id IS NOT NULL),
  FOREIGN KEY (state_id) REFERENCES places(place_id),
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);
Run Code Online (Sandbox Code Playgroud)

在关系理论方面,多态关联违反了第一范式,因为popular_place_id它实际上是一个有两个含义的列:它是一个国家或一个国家.你不会存储个人的age和他们phone_number在一列,出于同样的原因,你不应该同时存储state_idcountry_id在单个列.这两个属性具有兼容的数据类型的事实是巧合的; 它们仍然表示不同的逻辑实体.

多态关联也违反了第三范式,因为列的含义取决于为外键引用的表命名的额外列.在第三范式中,表中的属性必须仅依赖于该表的主键.


来自@SavasVedova的评论:

我不确定在没有看到表定义或示例查询的情况下遵循您的描述,但听起来您只是有多个Filters表,每个表都包含一个引用中央Products表的外键.

CREATE TABLE Products (
  product_id INT PRIMARY KEY
);

CREATE TABLE FiltersType1 (
  filter_id INT PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

CREATE TABLE FiltersType2 (
  filter_id INT  PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

...and other filter tables...
Run Code Online (Sandbox Code Playgroud)

如果您知道要加入的类型,则可以轻松地将产品加入特定类型的过滤器:

SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
Run Code Online (Sandbox Code Playgroud)

如果希望过滤器类型是动态的,则必须编写应用程序代码来构造SQL查询.SQL要求在编写查询时指定并修复表.您无法根据在各行中找到的值动态选择联接表Products.

唯一的另一种选择是使用外连接加入所有过滤表.那些没有匹配的product_id的东西只会作为一行空值返回.但是您仍然必须对所有连接的表进行硬编码,如果添加新的过滤表,则必须更新代码.

SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...
Run Code Online (Sandbox Code Playgroud)

加入所有过滤表的另一种方法是串行执行:

SELECT * FROM Product
INNER JOIN FiltersType1 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType3 USING (product_id)
...
Run Code Online (Sandbox Code Playgroud)

但是这种格式仍然需要您编写对所有表的引用.没有解决这个问题.

  • 我真的很喜欢"CONSTRAINT CHECK".但如果我们将"OR"改为"XOR",它可以得到改善.这样我们确保set中只有一列是NOT NULL (5认同)

Abi*_*bie 10

这不是世界上最优雅的解决方案,但您可以使用具体的表继承来实现此功能.

从概念上讲,你提出了一类"可以成为流行区域的东西"的概念,你的三种类型的地方都可以从中继承.你可以代表这是一个被称为表,例如,places其中每行中有一个排的一对一的关系regions,countriesstates.(区域,国家或州之间共享的属性,如果有的话,可以推送到此places表中.)popular_place_id然后,您将成为places表中行的外键引用,然后引导您进入某个地区,国家/地区或州.

你提出的第二列描述关联类型的解决方案恰好是Rails如何处理多态关联,但我不是一般的粉丝.Bill详细解释了为什么多态关联不是你的朋友.


Per*_*DBA 6

相关答案

\n

注意这个mysql标签,它意味着,因为 SQL 是 Codd关系模型relational中定义的数据子语言。

\n
    \n
  • 解决方案简单直接,我们在RM之前就已经有了之前就有了它,并且自 1981 年以来我们就有了关系型解决方案。
  • \n
  • 关系解决方案提供引用完整性(物理,在 SQL 级别)和关系完整性(逻辑)。
  • \n
  • 遵守开放架构标准(健全性)、所有约束;商业规则; 管理数据以及所有事务的等应部署在数据库中,而不是框架中,而不是应用程序 GUI 中,而不是应用程序中间层中。\xc2\xa0\xc2\xa0请注意,它是单个恢复单元。
  • \n
\n

polymorphic-associations标签是错误的,OP 没有要求它。\xc2\xa0\xc2\xa0将其强制转换为 OO/ORM 思维方式,然后在该思维方式中证明解决方案,超出了问题的范围。

\n
    \n
  • 此外,它需要一个框架和代码来强制执行约束;等等,在数据库之外,这是不合标准的。
  • \n
  • 此外,它不具备关系解决方案的基本完整性,更不用说关系完整性了。
  • \n
  • 此外,它违反了1NF3NF(如卡万答案中详述)。
  • \n
  • 空值是标准化错误,永远不应该存储它们。
  • \n
  • NullableFOREIGN KEY是一个严重的标准化错误。
  • \n
\n

解决方案

\n
\n

好吧,这是我的问题我有三个表;地区、国家、州。国家可以在区域内,州也可以在区域内。地区是食物链的顶端。

\n
\n

使其具有关联性

\n

让我们了解一下在关系上下文中这是什么。\xc2\xa0\xc2\xa0它是一个典型的表层次结构。

\n
    \n
  • 不要使用ID字段。\xc2\xa0\xc2\xa0不要将它们声明为PRIMARY KEY,这只会让您感到困惑,因为它不是键,它不提供关系模型中要求的行唯一性
  • \n
  • 密钥必须由数据组成
  • \n
  • 字段ID不是数据。\xc2\xa0\xc2\xa0始终是附加字段,附加索引
  • \n
  • 使用ID字段,您也许能够实现引用完整性(物理,SQL),但您没有机会实现关系完整性(逻辑)
  • \n
  • 有关完整讨论,包括 SQL 代码,请参阅:
    \n创建具有 2 个不同 auto_increment 的关系表,仅 \xc2\xa71 和 2。
  • \n
\n

基表

\n

富

\n

符号

\n
    \n
  • 我的所有数据模型都在IDEF1X中呈现,IDEF1X 是关系数据建模的表示法,我们自 1980 年\xe2\x80\x99s 早期以来就一直使用该表示法,并于 1993 年制定了关系数据建模标准,最后一次更新于 2016 年。

    \n
  • \n
  • 对于刚接触关系模型或其建模方法的人来说,IDEF1X 简介是必不可少的读物。\xc2\xa0\xc2\xa0请注意,IDEF1X 模型细节丰富且精度高,展示了所有内容必需的细节,而本土模型,不知道标准的命令,定义要少得多。\xc2\xa0\xc2\xa0这意味着需要完全理解该符号。\xc2\xa0\xc2\xa0

    \n
  • \n
  • ERD 不是标准,它不支持关系模型,并且完全不足以进行建模。

    \n
  • \n
  • 学者和“教科书”教导和推销反关系为“关系”是犯罪行为。

    \n
  • \n
\n

亚型

\n
\n

现在我正在添加一个包含两列的popular_areas表;region_id 和popular_place_id。是否可以使popular_place_id成为国家或州的外键。

\n
\n

完全没问题。\xc2\xa0\xc2\xa0关系模型建立在数学上;逻辑,它完全是逻辑的。\xc2\xa0\xc2\xa0ORXOR是逻辑的基础。\xc2\xa0\xc2\xa0在关系或SQL范例中,它被称为子类型集群。

\n
    \n
  • 即使在不兼容 SQL 的免费软件“SQL”中,它也是通过完全引用完整性完成的

    \n
      \n
    • 认为它无法完成,或者它需要学术界推销的可怕的额外字段和索引的想法是错误的。
    • \n
    \n
  • \n
  • 有关完整的实现详细信息,包括 SQL 代码的链接,请参阅子类型文档。

    \n
  • \n
  • 有关示例和讨论,请参阅:
    \n如何在子类型中实现引用完整性

    \n
  • \n
  • 为了澄清混淆这个问题以及其他答案的问题:
    \n书籍图表的关系模式

    \n
  • \n
\n
\n

我可能必须添加一个popular_place_type列来确定id是否描述一个国家或州。

\n
\n

正确,你在逻辑上思考。\xc2\xa0\xc2\xa0这里我们需要一个异或门,这就需要一个鉴别器

\n

添加餐位表

\n

富

\n

关系完整性

\n

参照完整性是 SQL 中提供的物理特征,而关系完整性是逻辑的,位于其之上(正确建模时,逻辑先于物理)。\xc2\xa0\xc2\xa0

\n

这是关系完整性的一个很棒、简单的示例。\xc2\xa0\xc2\xa0注意FOREIGN KEY子类型中的第二个。

\n
    \n
  • PlaceCountry被限制为与以下Country相同的aRegionPlace.Region

    \n
  • \n
  • PlaceState被限制为与以下State相同的aRegionPlace.Region

    \n
  • \n
  • 请注意,这仅适用于关系键(复合)

    \n
      \n
    • 在原始的记录归档系统中,关系完整性是不可能的,该系统以ID字段为“键”,并被学者和作者大力推销为“关系”
    • \n
    • 在这样的原始文件(它们不是表)中,PlaceCountry将允许任何Country,它不能被限制为Country与 相同的Regiona Place.Region
    • \n
    \n
  • \n
\n


one*_*hen 5

这是对Bill Karwin的"超级"方法的修正,使用复合键( place_type, place_id )来解决感知到的正常形式违规:

CREATE TABLE places (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) NOT NULL
     CHECK ( place_type = 'state', 'country' ),
  UNIQUE ( place_type, place_id )
);

CREATE TABLE states (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'state' NOT NULL
     CHECK ( place_type = 'state' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to states go here
);

CREATE TABLE countries (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'country' NOT NULL
     CHECK ( place_type = 'country' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to country go here
);

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  UNIQUE ( user_id, place_id ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
);
Run Code Online (Sandbox Code Playgroud)

这种设计无法确保每行中places存在一行statescountries(但不是两者).这是SQL中外键的限制.在完全符合SQL-92标准的DBMS中,您可以定义可延迟的表间约束,这些约束允许您实现相同但却很笨重,涉及事务并且这样的DBMS尚未将其推向市场.