在一对多表中指示“规范”记录

时间:2012-08-16 04:02:39

标签: sqlite database-design normalization

想象一下,我们有一张国家表和一张城市表。一个国家当然可以拥有许多城市,但一个城市只能在一个国家,因此一对多关系具有直观意义:

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列capital添加到countries,该列不能为null。亲:总是一个城市;骗局:与城市没有关系,没有任何强制执行城市的国家,或者甚至存在。

  2. 将一个布尔列capital添加到cities,如果为true则表示该城市是相关国家/地区的首府。 Pro:直接与相关城市相关联,没有重复的列表示层次结构;骗局:非常肯定这是不正常的正常化,因为没有什么能阻止某个国家的零或一个“资本”。

  3. 使用列capitalscountry以及两列(或至少在city上)的唯一约束创建附加表city。亲:感觉更干净,更容易加入countriescities;骗局:仍然不能确保城市在国内,或者是否存在。

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

    我目前正在使用SQLite,但无论基础数据库如何,我都对通用答案感兴趣。

    我做了一点挖掘并找到了Indicating primary/default record in database,但我不认为这真的回答了我的问题。


    PS:如果没有资本(可能没有城市!)就没那么糟糕了,但如果有多个城市那就不好了。

3 个答案:

答案 0 :(得分:0)

为了清晰和简单,我将布尔值IsCapital列添加到cities表中。然后添加一个触发器,设置所有其他城市(共享更新记录的国家/地区)当IsCapital在记录上设置为true时IsCapital = false。这将解决您的大多数问题。确保每个国家只有一个资本的一个案例是不可能的,你可以确保有0或1,但由于城市表对国家有一个FK约束,总会有一个时间点插入的国家将没有可以设置为国会大厦的城市。

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

答案 1 :(得分: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)
);

在该表中,主键约束保证每个国家/地区的资本不得超过一个。外键约束保证您选择的资本存在于您选择的国家/地区。在引用的表(“cities”表)中,您还需要{city_id,country_id}上的唯一约束;由于{city_id}在“城市”表中是唯一的,因此{city_id,country_id}在该表中也必然是唯一的,所以这不是问题。

保证国家与首都之间一对一关系(不是一对一或一对一关系)的声明性“方式”是使用断言。但我不知道任何支持CREATE ASSERTION的SQL dbms。这迫使我们依赖其中的一个或多个:

  • 触发并可能延迟约束,
  • 应用程序代码,或
  • 行政程序。

(最初,您必须在单个事务中的三个表“countries”,“cities”和“captials”中输入一行以满足所有约束。我认为您需要延迟约束为此,但我今天还没喝咖啡。)

答案 2 :(得分:0)

为了确保每个国家只有一个资本且资本不是来自不同国家的城市,请执行以下操作:

enter image description here

请注意我们如何使用识别关系将COUNTRY_ID迁移到CITY的PK,因此可以将其迁移回CONTRY - 这就是保证资本必须实际属于该国家/地区的资本资本。

此处的循环引用可防止插入新数据,如果DBMS支持,则使用延迟外键解析新数据。否则,您可以将COUNTRY.CAPITAL_NO保留为NULL(并在应用程序级别强制执行最终非NULL-ness)。 1


1 这假定DBMS具有MATCH SIMPLE外键(即,如果其任何组件为NULL,则忽略FK)。如果DBMS仅支持MATCH PARTIAL或FULL(例如MS Access),那么你就不幸了,并且必须通过非声明性手段(触发器或应用程序代码)来模拟FK。