我的问题是我有三张桌子;地区、国家、州.国家可以在区域内,国家可以在区域内.地区是食物链的顶端.
现在我添加了一个流行的带有两列的_areas表;region_id和popular_place_id.是否可以使popular_place_id成为任何一个国家OR个州的外键.我可能需要添加一个流行的_place _type列来确定id是否以任何方式描述一个国家或州.
我的问题是我有三张桌子;地区、国家、州.国家可以在区域内,国家可以在区域内.地区是食物链的顶端.
现在我添加了一个流行的带有两列的_areas表;region_id和popular_place_id.是否可以使popular_place_id成为任何一个国家OR个州的外键.我可能需要添加一个流行的_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约束对多态关联进行建模.外键约束始终引用one个目标表.
Rails和Hibernate等框架支持多态关联.但他们明确表示,必须禁用SQL约束才能使用此功能.相反,应用程序或框架必须做同等的工作,以确保满足引用.也就是说,外键中的值出现在一个可能的目标表中.
多态关联在增强数据库一致性方面很弱.数据完整性取决于所有访问数据库的客户机都使用相同的引用完整性逻辑,而且执行必须没有bug.
以下是一些利用数据库强制引用完整性的替代解决方案:
Create one extra table per target.例如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),
);
这意味着,要获得用户最喜欢的所有位置,需要同时查询这两个表.但这意味着你可以依靠数据库来加强一致性.
Create a 100 table as a supertable.正如Abie提到的,第二种 Select 是,你的热门位置引用一个表,比如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)
);
Use two columns.使用两列,而不是一列引用两个目标表中的任何一个.这两列可能是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
.这两个属性具有兼容的数据类型这一事实是巧合;它们仍然表示不同的逻辑实体.
多态关联也违反了Third Normal Form,因为列的含义取决于外键引用的表的额外列名称.在第三范式中,表中的属性必须仅依赖于该表的主键.
@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
行中的值动态 Select 联接表.
唯一的另一个选项是使用外部联接连接到all个筛选表.那些没有匹配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)
...
但这种格式仍然要求您写入对所有表的引用.这是绕不过go 的.