如何使用非唯一外键强制执行数据库完整性?

时间:2009-10-09 12:47:03

标签: sql sql-server foreign-keys unique

我希望有一个数据库表来保存包含修订历史记录的数据(比如Wikipedia上的页面)。我认为一个好主意是有两列标识行:(name, version)。所以样本表看起来像这样:

TABLE PERSONS:
    id:      int,
    name:    varchar(30),
    version: int,
    ... // some data assigned to that person.

因此,如果用户想要更新人员的数据,他们就不会进行更新 - 相反,他们会创建一个具有相同name但不同version值的新PERSONS行。向用户显示的数据(对于给定的name)是具有最高version的数据。

我有第二张表,比如说DOGS,它引用了PERSONS表中的人物:

TABLE DOGS:
    id:         int,
    name:       varchar(30),
    owner_name: varchar(30),
    ...

显然,owner_name是对PERSONS.name的引用,但我不能将其声明为外键(在MS SQL Server中),因为PERSONS.name不是唯一的!

问题:那么,在MS SQL Server 2008中,我应该如何确保数据库的完整性(即,对于每个DOG,PERSONS中至少存在一行,使得其PERSON.name = = DOG.owner_name)?

我正在寻找最优雅的解决方案 - 我知道我可以在PERSONS表上使用触发器,但这并不像我希望的那样具有声明性和优雅性。有什么想法吗?


其他信息

上面的设计具有以下优点:如果我需要,我可以“记住”一个人当前的id(或(name, version)对)并且我确信该行中的数据永远不会改变了。这很重要,例如如果我将此人的数据作为随后打印的文档的一部分,并且在5年内有人可能希望打印完全未更改的副本(例如,使用与今天相同的数据),那么这对他们来说非常容易

也许您可以想到一个完全不同的设计,它可以实现相同的目的,并且可以更容易地实施其完整性(最好使用外键或其他约束)?


编辑:感谢Michael Gattuso的回答,我发现了另一种可以描述这种关系的方式。有两种解决方案,我将其作为答案发布。请投票选出你喜欢哪一个。

8 个答案:

答案 0 :(得分:5)

在父表中,在(id,version)上创建唯一约束。将版本列添加到子表,并使用检查约束以确保它始终为0.使用FK约束将(parentid,version)映射到父表。

答案 1 :(得分:2)

或者,您可以维护具有历史价值的数据的人员历史记录表。通过这种方式,您可以保持人员和狗的表格整洁,参考文献简单,但也可以访问历史上有趣的信息。

答案 2 :(得分:1)

好的,首先,您需要规范化表格。谷歌“数据库规范化”,你会得到大量的阅读。特别是PERSONS表需要注意。

第二件事是,当您创建外键引用时,99.999%的时间要引用ID(数字)值。即,[DOGS]。[所有者]应该是对[PERSONS]的引用。[id]。

编辑:添加示例模式(原谅松散的语法)。我假设每只狗只有一个主人。这是实现人员历史的一种方式。所有列都不为空。

Persons Table:
int Id
varchar(30) name
...

PersonHistory Table:
int Id
int PersonId (foreign key to Persons.Id)
int Version (auto-increment)
varchar(30) name
...

Dogs Table:
int Id
int OwnerId (foreign key to Persons.Id)
varchar(30) name
...

最新版本的数据将直接存储在Persons表中,旧数据存储在PersonH​​istory表中。

答案 3 :(得分:0)

我会使用和关联表将多个版本链接到一个pk。

答案 4 :(得分:0)

我参与过的一个项目解决了类似的问题。这是一个生物记录数据库,随着新研究提高对分类学的理解,物种名称可以随时间发生变化。

然而,旧记录需要与原始物种名称保持相关。它变得复杂,但基本的解决方案是有一个NAME表,它只包含所有独特的物种名称,一个代表实际物种的物种表和一个将两者联系在一起的NAME_VERSION表。在任何时候都会有一个首选名称(即物种当前接受的科学名称),这是name_version中保存的布尔字段。

在您的示例中,这将转换为Details表(detailsid,otherdetails列)名为DetailsVersion(detailsid,personid)的链接表和Person表(personid,非更改数据)。将狗与人联系。

答案 5 :(得分:0)

id(int),
名,
.....
activeVersion(这将是来自personVersionInfo的UID)

注意:上面的表每个人都有1行。将有创建人的原始信息。

PersonVersionInfo

UID(识别人+版本的唯一标识符),
id(int),
名,
.....
versionId(这将为每个人生成)

DogID,
DogName
......

PersonsWithDogs

UID,
DogID

编辑:你必须加入PersonWithDogs,PersionVersionInfo,Dogs才能全面了解(截至今天)。这种结构将帮助您将狗链接到所有者(具有特定版本)。

如果Person的信息发生变化并且您希望获得与Dog相关的最新信息,则必须更新PersonWithDogs表以获得给定Dog的所需UID(人员)。

您可以在PersonWithDogs中使用DogID等限制 在这种结构中,UID(人)可以有很多狗。

您的方案(可以更改/限制等)将有助于更好地设计架构。

答案 6 :(得分:0)

感谢Michael Gattuso的回答,我发现了另一种可以描述这种关系的方式。有两种解决方案,这是第一种。请投票选出你喜欢哪一个。

解决方案1 ​​

在PERSONS表中,我们只保留名称(唯一标识符)和当前人员数据的链接:

TABLE PERSONS:
    name:            varchar(30),
    current_data_id: int

我们创建了一个新表PERSONS_DATA,其中包含该人的所有数据历史记录:

TABLE PERSONS_DATA:
    id:      int
    version: int (auto-generated)
    ... // some data, like address, etc.

DOGS表保持不变,它仍然指向一个人的名字(FK到PERSONS表)。

优点:对于每只狗,至少存在一个包含其所有者数据的PERSONS_DATA行(这就是我想要的)

DISADVANTAGE:如果您想更改某人的数据,您必须:

  1. 添加新的PERSONS_DATA行
  2. 更新此人的PERSONS条目以指向新的PERSONS_DATA行。

答案 7 :(得分:0)

感谢Michael Gattuso的回答,我发现了另一种可以描述这种关系的方式。有两种解决方案,这是第二种解决方案。请投票选出你喜欢哪一个。

解决方案2

在PERSONS表中,我们只保留名称(唯一标识符)和第一个(非当前!)人员数据的链接:

TABLE PERSONS:
    name:            varchar(30),
    first_data_id: int

我们创建了一个新表PERSONS_DATA,其中包含该人的所有数据历史记录:

TABLE PERSONS_DATA:
    id:      int
    name:    varchar(30)
    version: int (auto-generated)
    ... // some data, like address, etc.

DOGS表保持不变,它仍然指向一个人的名字(FK到PERSONS表)。

优点:

  • 对于每只狗,至少有一个PERSONS_DATA行包含其所有者的数据(这就是我想要的)
  • 如果我想更改某人的数据,我不必更新PERSONS行,只需添加新的PERSONS_DATA行

DISADVANTAGE:要检索当前人的数据,我必须:

  • 选择具有给定名称和最高版本(可能很昂贵)的PERSONS_DATA
  • 选择具有特殊版本的PERSONS_DATA,例如“-1”,但是每次添加新的PERSONS_DATA时我都要更新两个PERSONS_DATA行,在这个解决方案中我想避免更新2行...

您怎么看?