2012-08-16 37 views
3

想象一下,我们有一张国家表和一张城市表。当然,一个国家可以有很多城市,但一个城市只能是一个国家,这样一个一对多的关系,具有直观意义:指示一对多表中的“规范”记录

countries 
| id | name | 
| 1 | Lorwick | 
| 2 | Belmead | 

cities 
| id | country | name  | 
| 1 |  1 | Marblecrest | 
| 2 |  1 | Westacre | 
| 3 |  2 | Belcoast | 
| 4 |  1 | Rosemarsh | 
| 5 |  2 | Vertston | 

但是除了我们的一个一对多的关系,我们想描述一下国家首都的一对一关系。如果重要的话,假设首都可能会定期改变,而城市可能随意出现并消失,而城市可能会改变国家。点是,这个数据是不稳定的。

我看到一对夫妇的选择:

  1. 添加int列capitalcountries不能为空。临:总是一个城市; Con:与城市没有关系,在这个国家没有强制执行这个城市,或者它甚至存在。

  2. 添加布尔列capitalcities,如果为true则表示城市是关联国家的首都。专业:直接与有问题的城市相关联,没有重复的列指示层次结构;康:很确定这是正常化程度很差,因为没有什么东西可以阻止一个国家​​的零或一个以上的“资本”。

  3. 创建一个额外的表capitals与列countrycity和两列(或至少在city)上的唯一约束。 Pro:在countriescities上感觉更干净,易于加入; Con:仍然不能确保城市在国内,或者不存在。

什么是最规范化和/或最好的方式来表示这种关系?有没有办法确保每个国家都有一个实际存在并居住在该国的资本?我想这是不可能的,在这种情况下,我怎样才能最大限度地减少我的客户端代码的问题?

我目前正在使用SQLite,但是我对广义答案感兴趣,无论底层数据库如何。

我做了一点挖掘,发现Indicating primary/default record in database,但我不认为这真的回答了我的问题。注:如果没有资金(可能没有城市!),这并不坏,但如果有多个,那就不好了。

回答

0

为了清晰和简单,我将布尔IsCapital列添加到城市表中。然后添加设置所有其他城市(共享更新记录的国家/地区)的触发器IsCapital = IsCapital在记录上设置为true时为false。这将处理你的大部分顾虑。确保每个国家只有一个资本的情况并不是真的可行,可以确保有0或1,但由于城市表对国家有FK约束,因此总会有一个时间点插入的国家将没有可以设置为国会大厦的城市。

FWIW,我认为逻辑应该留给应用程序,对数据库的参照完整性。

+0

我只是做了类似的问题进行搜索,发现这个答案 http://stackoverflow.com/a/638947/45767 (他的回答加强为什么我认为逻辑的应用所属的) – JeremyWeir 2012-08-16 04:58:41

0

我认为“每个国家只有一个资本”的要求与“城市出现并随意消失”的要求相冲突。如果一个城市能够消失,那么首都城市也会消失。

您可以强制执行约束条件:“每个国家具有[零或]一个实际上存在并驻留在该国家内的资本”,并在资本表上具有外键约束。

create table capitals (
    country_id integer primary key, 
    city_id integer not null, 
    foreign key (country_id, city_id) references cities (country_id, city_id) 
); 

在该表中,主键约束保证可以有每个国家不超过一个资本。外键约束保证您选择的资本存在于您选择的国家。在所引用的表格(“城市”表格)中,您还需要对{city_id,country_id}有一个唯一的限制;由于{city_id}在“城市”表中是唯一的,{city_id,country_id}在该表中也必须是唯一的,所以这不是问题。

确保国家和首都之间一对一关系(而非一对一或一对一关系)的声明性“方法”是使用断言。但我不知道任何支持CREATE ASSERTION的当前SQL dbms。这迫使我们依靠一个或更多的这些:

  • 触发器和可能推迟的约束,
  • 应用程序代码,或
  • 行政程序。

(最初,你必须在三个表“的国家”,以满足所有的约束条件,以输入一行在一个单一的交易,“城市”和“captials”我想你会。需要为推迟的约束,但我还没有咖啡但今天)

0

为了确保恰好有每个国家资本及资本不是来自不同国家的一个城市,这样做:

enter image description here

请注意我们如何使用识别关系将COUNTRY_ID迁移到CITY的PK,所以它可以迁移回CONTRY--这是保证资本必须属于它所属的国家的资本。

这里的循环引用阻止插入新数据,如果DBMS支持它们,则使用延迟外键解决这个问题。否则,您可以将COUNTRY.CAPITAL_NO设置为NULL(并且在应用程序级别强制执行其最终的非NULL值)。


这假定DBMS具有MATCH SIMPLE外键(即FK是如果任何组件都为NULL忽略)。如果DBMS仅支持MATCH PARTIAL或FULL(例如MS Access),那么您运气不好,并且必须通过非声明方式(触发器或应用程序代码)来模拟FK。