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

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

现在我添加了一个有两列的popul_areas表;Region_id和popul_place_id。是否有可能使popul_place_id成为任一国家< em >或< / em >州的外键。我可能必须添加一个popul_place_type列,以确定id是描述一个国家还是一个州。

78547 次浏览

这不是世界上最优雅的解决方案,但你可以使用具体表继承来实现这个工作。

从概念上讲,你提出了一类“可以成为热门领域的东西”的概念,你的三种类型的地方都继承了这些概念。您可以将其表示为一个表,例如,places,其中每行与regionscountriesstates中的行有一对一的关系。(地区、国家或州之间共享的属性(如果有的话)可以推入这个位置表。)你的popular_place_id将是对places表中一行的外键引用,它将引导你到一个地区、国家或州。

您在第二篇专栏文章中提出的描述关联类型的解决方案恰好是Rails如何处理多态关联,但我并不喜欢这种方法。Bill非常详细地解释了为什么多态关联不是你的朋友。

你所描述的被称为多态关联。也就是说,“外键”列包含一个必须存在于一组目标表中的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
);

没有办法使用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),
);

这意味着要获得用户最喜欢的所有地方,您需要查询这两个表。但这意味着您可以依赖数据库来执行一致性。

正如Abie提到的,第二种选择是你的常用位置引用像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)
);

使用两列。使用两个列,而不是一个列可以引用两个目标表中的任何一个。这两列可以是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)
);

就关系理论而言,多态关联违反了第一范式,因为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...

如果你知道你想要加入哪种类型的过滤器,将产品连接到特定类型的过滤器是很容易的:

SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)

如果希望筛选器类型是动态的,则必须编写应用程序代码来构造SQL查询。SQL要求在编写查询时指定并固定表。不能根据Products的各个行中找到的值动态地选择联接表。

唯一的其他选项是使用外部连接连接到所有筛选表。那些没有匹配product_id的函数将作为一行null返回。但是你仍然必须硬编码所有连接的表,如果你添加新的过滤表,你必须更新你的代码。

SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...

连接到所有过滤表的另一种方法是连续执行:

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)
...

但是这种格式仍然要求您编写对所有表的引用。这是无法回避的。

以下是对Bill Karwin的“supertable”方法的更正,使用复合键( 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 )
);

此设计不能确保对于places中的每一行都存在statescountries中的一行(但不能同时存在)。这是SQL中外键的一个局限性。在一个完全符合SQL-92标准的DBMS中,你可以定义可延迟的表间约束,这将允许你实现同样的目标,但它很笨拙,涉及事务,这样的DBMS还没有推向市场。

我意识到这个帖子很旧了,但我看到了这个,我想到了一个解决方案,我想我应该把它扔出去。

区域、国家和州是存在于层次结构中的地理位置。

您可以通过创建一个名为geography _location_type的域表来完全避免这个问题,您可以用三行(Region、Country、State)填充该表。

接下来,创建一个具有外键geography _location_type_id的地理位置表,而不是三个位置表(这样您就知道实例是Region、Country还是State)。

通过使这个表自引用来建模层次结构,这样一个State实例将fKey保存到它的父Country实例,而Country实例又将fKey保存到它的父Region实例。Region实例的fKey值为NULL。这与您使用三个表所做的没有什么不同(您将在地区和国家之间以及国家和州之间有1 -许多关系),只是现在它们都在一个表中。

popul_user_location表将是user和georgraphical_location之间的范围解析表(因此许多用户可能喜欢许多地方)。

如此如此……

enter image description here

CREATE TABLE [geographical_location_type] (
[geographical_location_type_id] INTEGER NOT NULL,
[name] VARCHAR(25) NOT NULL,
CONSTRAINT [PK_geographical_location_type] PRIMARY KEY ([geographical_location_type_id])
)


-- Add 'Region', 'Country' and 'State' instances to the above table




CREATE TABLE [geographical_location] (
[geographical_location_id] BIGINT IDENTITY(0,1) NOT NULL,
[name] VARCHAR(1024) NOT NULL,
[geographical_location_type_id] INTEGER NOT NULL,
[geographical_location_parent] BIGINT,  -- self referencing; can be null for top-level instances
CONSTRAINT [PK_geographical_location] PRIMARY KEY ([geographical_location_id])
)


CREATE TABLE [user] (
[user_id] BIGINT NOT NULL,
[login_id] VARCHAR(30) NOT NULL,
[password] VARCHAR(512) NOT NULL,
CONSTRAINT [PK_user] PRIMARY KEY ([user_id])
)




CREATE TABLE [popular_user_location] (
[popular_user_location_id] BIGINT NOT NULL,
[user_id] BIGINT NOT NULL,
[geographical_location_id] BIGINT NOT NULL,
CONSTRAINT [PK_popular_user_location] PRIMARY KEY ([popular_user_location_id])
)


ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_type_geographical_location]
FOREIGN KEY ([geographical_location_type_id]) REFERENCES [geographical_location_type] ([geographical_location_type_id])






ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_geographical_location]
FOREIGN KEY ([geographical_location_parent]) REFERENCES [geographical_location] ([geographical_location_id])






ALTER TABLE [popular_user_location] ADD CONSTRAINT [user_popular_user_location]
FOREIGN KEY ([user_id]) REFERENCES [user] ([user_id])






ALTER TABLE [popular_user_location] ADD CONSTRAINT [geographical_location_popular_user_location]
FOREIGN KEY ([geographical_location_id]) REFERENCES [geographical_location] ([geographical_location_id])

不确定目标DB是什么;以上是MS SQL Server。

嗯,我有两张表:

  1. 歌曲

a)歌曲编号 b)歌名 …< / p >

    <李>播放列表 a)播放列表编号 b)播放列表标题 李…< / >

我有第三个

  1. songs_to_playlist_relation

问题是某些类型的播放列表有链接到其他播放列表。但在mysql中,我们没有与两个表相关联的外键。

我的解决方案:我将在songs_to_playlist_relation中放入第三列。这一列是布尔的。如果1然后歌曲,否则将链接到播放列表表。

所以:

  1. songs_to_playlist_relation

a) Playlist_number (int) b) Is song (boolean) c)相对数字(歌曲数量或播放列表数量)(int) (外键到任何表)

. bb0

 #创建表歌曲
查询。append("SET SQL_MODE = NO_AUTO_VALUE_ON_ZERO;")
查询。append("CREATE TABLE songs (NUMBER int(11) NOT NULL,SONG POSITION int(11) NOT NULL,PLAY SONG tinyint(1) NOT NULL DEFAULT ',SONG TITLE varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL NULL,ARTIST varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστος κα (255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστος στιχουργό ζ ',COMPOSER varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστος σ ν ν 8 COLLATE utf8_general_ci NOT NULL DEFAULT ',ALBUM varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστο α λμπο μ', NUMBER2 varchar(600) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,NUMBER3 varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,NUMBER4 int(11) NOT NULL DEFAULT '0',NUMBER5 float NOT NULL DEFAULT '1',NUMBER6 float NOT NULL DEFAULT '1')引擎=InnoDB DEFAULT CHARSET=utf8;")
查询。追加("ALTER TABLE songs添加主键(NUMBER),添加唯一键POSITION (SONG POSITION),添加唯一键TITLE (SONG TITLE),添加唯一键PATH (SONG PATH);")
查询。("ALTER TABLE songs MODIFY NUMBER int(11) NOT NULL AUTO_INCREMENT;")

. append("ALTER TABLE songs MODIFY NUMBER int(11) NOT NULL AUTO_INCREMENT;"
#create table playlists
queries.append("CREATE TABLE `playlists` (`NUMBER` int(11) NOT NULL,`PLAYLIST POSITION` int(11) NOT NULL,`PLAYLIST TITLE` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`PLAYLIST PATH` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;")
queries.append("ALTER TABLE `playlists` ADD PRIMARY KEY (`NUMBER`),ADD UNIQUE KEY `POSITION` (`PLAYLIST POSITION`),ADD UNIQUE KEY `TITLE` (`PLAYLIST TITLE`),ADD UNIQUE KEY `PATH` (`PLAYLIST PATH`);")
queries.append("ALTER TABLE `playlists` MODIFY `NUMBER` int(11) NOT NULL AUTO_INCREMENT;")


#create table for songs to playlist relation
queries.append("CREATE TABLE `songs of playlist` (`PLAYLIST NUMBER` int(11) NOT NULL,`SONG OR PLAYLIST` tinyint(1) NOT NULL DEFAULT '1',`RELATIVE NUMBER` int(11) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;")
queries.append("ALTER TABLE `songs of playlist` ADD KEY `PLAYLIST NUMBER` (`PLAYLIST NUMBER`) USING BTREE;")
queries.append("ALTER TABLE `songs of playlist` ADD CONSTRAINT `playlist of playlist_ibfk_1` FOREIGN KEY (`PLAYLIST NUMBER`) REFERENCES `playlists` (`NUMBER`) ON DELETE RESTRICT ON UPDATE RESTRICT")

这是所有!< / p >
playlists_query = "SELECT s1.*, s3.*, s4.* FROM songs as s1 INNER JOIN `songs of playlist` as s2 ON s1.`NUMBER` = s2.`RELATIVE NUMBER` INNER JOIN `playlists` as s3 ON s3.`NUMBER` = s2.`PLAYLIST NUMBER` INNER JOIN `playlists` as s4 ON s4.`NUMBER` = s2.`RELATIVE NUMBER` ORDER BY s3.`PLAYLIST POSITION`,`s1`.`SONG POSITION`"

关系的答案

注意mysql标记,它表示relational,因为SQL是Codd的< >强关系模型< / >强中定义的数据子语言。

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

polymorphic-associations标记是假的,op没有请求它。强迫它进入OO/ORM思维模式,然后在这种思维模式下证明一个解决方案,超出了问题的范围。

  • 此外,它需要一个框架和代码来实施约束;等等,在数据库之外,这是不合标准的。
  • 此外,它不具有关系解决方案的基本完整性,更不用说关系完整性了。
  • 此外,它违反了1NF3NF(详见Karvan Answer)。
  • null是一个归一化错误,它们不应该被存储。
  • 一个Nullable FOREIGN KEY是一个严重的规范化错误。

解决方案

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

要有关系

让我们来理解在关系上下文中这是什么。它是典型的表层次结构。

  • 不要使用ID字段。不要将它们声明为PRIMARY KEY,这只会让你感到困惑,因为它不是一个Key,它没有提供行独特性,就像关系模型
  • Key必须是由数据组成
  • ID字段不是数据。它总是一个额外的字段和一个额外的索引
  • 使用ID字段,你可能能够实现引用完整性(物理的,SQL),但你没有机会实现关系完整性(逻辑的)
  • 完整的讨论,包括SQL代码,请参见:
    创建2个不同auto_increment的关系表,§1 &李2只。< / >

基表

Foo

符号

  • 我所有的数据模型都是在IDEF1X中呈现的,关系数据建模的符号,我们从20世纪80年代早期就有了,在1993年制定了关系数据建模标准,最近一次更新是在2016年。

  • 对于那些不熟悉关系模型或其建模方法的人来说,IDEF1X介绍是必不可少的阅读。请注意,IDEF1X模型具有丰富的细节和精度,显示了所有所需的细节,而一个自产模型,不知道标准的必要性,定义要少得多。这意味着,符号需要完全理解。

  • ERD不是一个标准,它不支持关系模型,它完全不适合建模。

  • 学术和“教科书”;将反关系教育和营销为“关系”;是罪犯。

子类型

现在我添加了一个有两列的popul_areas表;Region_id和popul_place_id。是否有可能使popul_place_id成为国家或州的外键。

没问题。关系模型是建立在数学;逻辑,完全是逻辑。XOR门是逻辑的基础。在关系型或SQL范式中,它被称为子类型集群。

  • 即使在不兼容SQL的免费软件中,它也是完全参照完整性完成的

    • 认为这无法实现的观点,或者认为它需要学术界推销的可怕的额外领域和指数,都是错误的。
  • 有关完整的实现细节,包括SQL代码的链接,请参阅< >强亚型< / >强文档。

  • 举例和讨论参见:
    如何在子类型中实现引用完整性 < / p >

  • 用于澄清混淆此问题的问题,从而澄清其他答案:
    图书图的关系模式 < / p >

我可能必须添加一个popul_place_type列,以确定id是描述一个国家还是一个州。

正确,你的思维很有逻辑。这里我们需要一个XOR门,它需要一个鉴频器

添加位置表

Foo

关系完整性

参考完整性是SQL中提供的物理特性,而关系完整性是逻辑的,是在它之上的(当建模正确时,逻辑的先于物理的)。

这是关系完整性的一个很好的、简单的例子。注意Subtypes中的第二个FOREIGN KEY

  • PlaceCountry被约束为与Place.Region在同一个Region中的Country

  • PlaceState被约束为与Place.Region在同一个Region中的State

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

    • 关系完整性在原始的记录归档系统中是不可能的,它的特征是ID字段作为“键”,并被学者和作者大量推销为“关系”。
    • 在这样的基元文件(它们不是表)中,PlaceCountry将允许任何Country,它不能被约束为与Place.Region在同一个Region中的Country