可以对两个可能的表之一执行MySQL外键吗?

可以对两个可能的表之一执行MySQL外键吗?,mysql,polymorphic-associations,Mysql,Polymorphic Associations,我的问题是,我有三张桌子;地区、国家、州。国家可以在区域内,国家可以在区域内。区域是食物链的顶端 现在我添加了一个流行的带有两列的_areas表;地区id和流行地点id。是否可以使流行地点id成为国家或州的外键。我可能需要添加一个流行的“地点”类型列,以确定id是否以任何方式描述一个国家或州。这不是世界上最优雅的解决方案,但您可以使用它来实现这一点 从概念上讲,你提出的是一类“可以成为流行领域的事物”的概念,你的三种地方继承了这类事物。您可以将其表示为一个名为“places的表,其中每一行与地区

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


现在我添加了一个流行的带有两列的_areas表;地区id和流行地点id。是否可以使流行地点id成为国家或州的外键。我可能需要添加一个流行的“地点”类型列,以确定id是否以任何方式描述一个国家或州。

这不是世界上最优雅的解决方案,但您可以使用它来实现这一点

从概念上讲,你提出的是一类“可以成为流行领域的事物”的概念,你的三种地方继承了这类事物。您可以将其表示为一个名为“places的表,其中每一行与
地区
国家
中的一行具有一对一的关系。(区域、国家或州之间共享的属性(如果有)可以推送到该位置表中。)然后,您的
热门位置\u id
将是位置表中一行的外键引用,该行将引导您到区域、国家或州


您在第二个专栏中提出的描述关联类型的解决方案恰好是Rails如何处理多态关联,但我一般不喜欢。比尔非常详细地解释了为什么多态关联不是你的朋友。

你所描述的被称为多态关联。也就是说,“外键”列包含一个id值,该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约束才能使用此功能。相反,应用程序或框架必须做相同的工作,以确保满足引用。也就是说,外键中的值存在于一个可能的目标表中

多态关联在增强数据库一致性方面很弱。数据完整性取决于访问数据库的所有客户机所执行的引用完整性逻辑相同,而且执行必须没有bug

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

为每个目标创建一个额外的表。例如
流行国家
流行国家
,分别引用
国家
国家
。这些“流行”表格中的每一个都引用了用户的个人资料

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),
);
这意味着要获得用户最喜欢的所有位置,您需要查询这两个表。但这意味着您可以依靠数据库来实现一致性

创建一个
places
表作为超级表。
正如Abie所提到的,第二种选择是,您的常用位置引用一个类似
places
的表,它是
州和
国家的父级。也就是说,州和国家/地区都有一个指向
位置的外键(您甚至可以将此外键设置为
州和
国家/地区的主键)

使用两列。使用两列,而不是一列引用两个目标表中的任何一个。这两列可以是
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\u place\u id
实际上是一个具有两种含义的列:它要么是一个州,要么是一个国家。你不能将一个人的
年龄
电话号码
存储在一列中,同样的原因,你也不能将
州id
国家id
都存储在一列中。这两个属性具有兼容的数据类型这一事实是巧合的;它们仍然表示不同的逻辑实体

多态关联也有冲突,因为列的含义取决于外键引用的表的额外列名称。在第三范式中,表中的属性必须仅依赖于该表的主键


@SavasVedova回复:

我不确定在没有看到表定义或示例查询的情况下是否遵循了您的描述,但听起来您只是有多个
过滤器
表,每个表都包含一个外键,该外键引用一个中心
产品

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要求在编写查询时指定并修复表。您不能根据
产品
的单个行中的值动态选择联接表

唯一的其他选项是使用外部联接联接到所有筛选器表。那些没有匹配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)
...
但这种格式仍然要求您写入对所有选项卡的引用
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)
...
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 )
);
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])
#create table songs queries.append("SET SQL_MODE =
NO_AUTO_VALUE_ON_ZERO
;") queries.append("CREATE TABLE
songs
(
NUMBER
int(11) NOT NULL,
SONG POSITION
int(11) NOT NULL,
PLAY SONG
tinyint(1) NOT NULL DEFAULT '1',
SONG TITLE
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
DESCRIPTION
varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
ARTIST
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστος καλλιτέχνης',
AUTHOR
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστος στιχουργός',
COMPOSER
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστος συνθέτης',
ALBUM
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστο άλμπουμ',
YEAR
int(11) NOT NULL DEFAULT '33',
RATING
int(11) NOT NULL DEFAULT '5',
IMAGE
varchar(600) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
SONG PATH
varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
SONG REPEAT
int(11) NOT NULL DEFAULT '0',
VOLUME
float NOT NULL DEFAULT '1',
SPEED
float NOT NULL DEFAULT '1') ENGINE=InnoDB DEFAULT CHARSET=utf8;") queries.append("ALTER TABLE
songs
ADD PRIMARY KEY (
NUMBER
), ADD UNIQUE KEY
POSITION
(
SONG POSITION
), ADD UNIQUE KEY
TITLE
(
SONG TITLE
), ADD UNIQUE KEY
PATH
(
SONG PATH
);") queries.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")
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`"