可能做一个MySQL外键的两个可能的表呢?
-
22-07-2019 - |
题
那么这里是我的问题,我有三个表;地区,国家,州。国家可区里,状态可以是区域内。区域是食物链的顶部。
现在我添加具有两列的表popular_areas; REGION_ID和popular_place_id。是否有可能使popular_place_id是一个外键无论是国家 或的的状态。我可能将不得不增加一个popular_place_type列,以确定该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和休眠支持。但他们明确地说,你必须禁用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)
);
在关系理论而言,多态关联违反第一范式时,因为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将只是返回空值的单行。但是你还是要硬编码的所有的连接表,如果你添加新的筛选器表,你需要更新你的代码。
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的是如何处理多态关联,但我不是的,在一般的风扇。比尔解释了出色的细节,为什么多态关联是不是你的朋友。
下面是校正比尔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符合标准的数据库管理系统,你可以缓征定义表间的约束,它会让你达到相同的,但它是笨重,涉及到交易,这样的数据库管理系统还没有做它对市场。
我知道这线程是老了,但我看到了这一点,一个解决方案来考虑,我想我会扔在那里。
区域,国家和国家是生活在一个层次地理位置。
您可以通过创建一个名为geographical_location_type域表,你将有三排(地区,国家,州)填充完全避免你的问题。
其次,代替三个位置的表,创建具有geographical_location_type_id的外键(所以你知道,如果该实例是一个地区,国家或国家)的单个geographical_location表。
模型的,这样一个国家实例中包含的Fkey其父国家实例,它反过来持有的Fkey其父地区的实例使该表自引用的层次结构。区域情况将在FKEY持有空。这不是比你会与三个表做不同的(你会1 - 区域和国家与国家和国家之间的多对多关系)。除了现在这一切都在一个表
在popular_user_location表将是用户和georgraphical_location之间的范围解析表(这么多用户可能喜欢的许多地方)。
... SOOOO
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服务器。