Answers:
您所描述的称为多态关联。也就是说,“外键”列包含一个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约束才能使用此功能。而是,应用程序或框架必须做等效的工作以确保满足引用。即,外键中的值存在于可能的目标表之一中。
多态关联在强制数据库一致性方面较弱。数据完整性取决于使用强制实施相同参照完整性逻辑的所有客户端访问数据库,并且强制实施必须没有错误。
以下是一些利用数据库强制引用完整性的替代解决方案:
为每个目标创建一个额外的表。例如popular_states
和popular_countries
,分别引用states
和countries
。这些“受欢迎”表中的每一个也都引用用户的个人资料。
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
表作为上级表。 由于艾比提到,第二个选择是你的热闹的地方引用类似的表格places
,这是父母双方states
和countries
。也就是说,这两个州和国家也有外键places
(你甚至可以使这个外键也是主键states
和countries
)。
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)
);
在关系理论方面,多态关联违反了First Normal Form,因为popular_place_id
它实际上是具有两个含义的列:它是一个州或一个国家。您不会将一个人age
及其一个人存储phone_number
在一个列中,并且出于同样的原因,您不应该同时存储两个人state_id
及其一个人。country_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)
...
但是这种格式仍然需要您编写对所有表的引用。没有解决的办法。
这不是世界上最优雅的解决方案,但是您可以使用具体的表继承来完成这项工作。
从概念上讲,您正在提出一类“可以成为流行区域的事物”的概念,您的三种类型的场所都可以从中继承。你可以代表这是一个被称为表,例如,places
其中每行有一个排的一对一的关系regions
,countries
或states
。(可以将在区域,国家或州之间共享的属性(如果有的话,推入到此位置表中。)popular_place_id
然后,您将成为对位置表中一行的外键引用,然后将您引至一个区域,国家或状态。
您在第二列中提出的用于描述关联类型的解决方案恰好是Rails如何处理多态关联,但是我一般不喜欢这种方式。Bill详细解释了为什么多态关联不是您的朋友。
这是对比尔·卡尔文(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 )
);
这种设计不能确保在中的每一行都places
存在states
或countries
(但不能同时存在)一行。这是SQL中外键的限制。在完全符合SQL-92标准的DBMS中,您可以定义可延期的表间约束,这些约束可以使您实现相同的目标,但它笨重,涉及事务,并且这种DBMS尚未投放市场。
我意识到这个线程很旧,但是我看到了这个想法,想到了一个解决方案,我想我应该把它扔在那里。
地区,国家和州是生活在层次结构中的地理位置。
您可以通过创建一个名为geo_location_type的域表来完全避免问题,该域表将填充三行(Region,Country,State)。
接下来,代替三个位置表,创建一个具有geo_location_type_id外键的单一geo_location表(这样您就知道实例是Region,Country还是State)。
通过使该表自引用来对层次结构进行建模,以使State实例将fKey保留为其父Country实例,而Country实例又将fKey保留为其父Region实例。区域实例在该fKey中将保留NULL。这与您对这三个表(您将拥有1 –区域与国家之间以及国家与州之间的许多关系)所做的操作没有什么不同,只不过现在全部放在一个表中了。
Popular_user_location表将是用户和georgraphical_location之间的范围解析表(因此,许多用户可能会喜欢许多地方)。
太…
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])
不确定目标数据库是什么;以上是MS SQL Server。
好吧,我有两个表:
a)歌曲编号b)歌曲标题...
我有三分之一
问题在于某些类型的播放列表具有指向其他播放列表的链接。但是在mysql中,我们没有与两个表关联的外键。
我的解决方案:我将在songs_to_playlist_relation中放置第三列。该列将为布尔值。如果为1,则为歌曲,否则将链接至播放列表。
所以:
a)播放列表编号(int)b)是歌曲(布尔值)c)相对编号(歌曲编号或播放列表编号)(int)(不是任何表的外键)
#创建表歌 查询。append (“ SET SQL_MODE =就这样!NO_AUTO_VALUE_ON_ZERO
;” ) 查询。append (“ CREATE TABLEsongs
(NUMBER
int(11)NOT NULL,SONG POSITION
int(11)NOT NULL,PLAY SONG
tinyint(1)NOT NULL缺省'1',SONG TITLE
varchar(255)字符集utf8 COLLATE utf8_general_ci NOT NULL,DESCRIPTION
varchar(1000)字符集utf8 COLLATE utf8_general_ci NOT NULL,ARTIST
VARCHAR(255)字符集UTF8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστοςκαλλιτέχνης',AUTHOR
VARCHAR(255)字符集UTF8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστοςστιχουργός',COMPOSER
VARCHAR(255)字符集UTF8 COLLATE utf8_general_ci NOT NULL默认'Άγνωστοςσυνθέτης',ALBUM
)YEAR
RATING
IMAGE
SONG PATH
SONG REPEAT
VOLUME
SPEED
songs
NUMBER
POSITION
SONG POSITION
TITLE
SONG TITLE
PATH
SONG PATH
查询。 int(11)NOT NULL AUTO_INCREMENT;”append(“ALTER TABLEsongs
MODIFY )NUMBER
#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。*从歌曲开始,作为s1 INNER JOIN`播放列表的歌曲作为s2 ONs1。`NUMBER`= s2。 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。”
join
目标也将发生变化……我是不是太复杂了?救命!