使用数据库1:1关系有意义吗?

前几天我在想正常化的问题,我突然想到,我想不出在什么时候数据库中应该存在1:1的关系。

  • Name:SSN? 我会把他们放在同一张桌子上。
  • PersonID:AddressID? 还是同一张桌子。

我可以想出无数个1: many 或 many: many (使用适当的中间表)的例子,但从来没有1:1的例子。

我漏掉了什么明显的东西吗?

44215 次浏览

一个原因是数据库效率。使用1:1关系允许您分割在行/表锁定期间将受到影响的字段。如果表 A 有大量更新,而表 B 有大量读操作(或者来自其他应用程序的大量更新) ,那么表 A 的锁定不会影响表 B 中的操作。

其他人提出了一个很好的观点。安全性也可以是一个很好的理由,这取决于应用程序等如何访问系统。我倾向于采用一种不同的方法,但它可能是限制对某些数据的访问的一种简单方法。在必要时拒绝访问某个表非常简单。

我在博客上写的。

就纯科学而言,是的,它们毫无用处。

在实际的数据库中,在单独的表中保留一个很少使用的字段有时是很有用的: 为了加速使用 this 和 only this 字段的查询; 为了避免锁等等。

如果您使用的数据与一个流行的 ORM 之一,您可能希望将一个表分解为多个表,以匹配您的对象层次结构。

与其使用视图来限制对字段的访问,不如将受限制的字段保存在单独的表中,只有某些用户可以访问这些表。

1:1关系通常表示出于某种原因对较大的实体进行了分区。通常是由于物理模式中的性能原因,但是如果同时期望大量数据是“未知的”(在这种情况下,您有1:0或1:1,但是没有更多) ,那么在逻辑方面也会发生这种情况。

举一个分区的例子: 你有一个雇员的数据,但是有一个更大的数据集需要被收集,当且仅当他们选择有健康保险时。我将把关于健康覆盖率的人口统计数据保存在不同的表中,这样既可以更容易地进行安全分区,又可以避免将这些数据拖到与保险无关的查询中。

物理分区的一个示例是托管在多个服务器上的相同数据。我可能将医疗保险覆盖率的人口统计数据保存在另一个州(比如人力资源办公室所在地) ,而主数据库可能只通过链接服务器链接到它... ... 避免将敏感数据复制到其他地方,但使其可用于(假设这里很少)需要它的查询。

物理分区可以是有用的 无论何时,您的查询需要较大实体的一致子集。

大多数情况下,它更多的是一种物理结构,而不是逻辑结构。它通常用于垂直分区表,以利用跨物理设备分割 I/O 或其他查询优化,这些优化与隔离访问频率较低的数据或数据有关,这些数据或数据需要比同一对象上的其他属性(SSN、 Salary 等)保持更安全。

规定1-1关系的唯一逻辑考虑是当某些属性仅适用于某些实体时。但是,在大多数情况下,有一种更好的/更规范化的方法来通过实体提取对数据进行建模。

仅在某些情况下需要的扩展信息。在遗留应用程序和编程语言(如 RPG)中,程序是通过表编译的(因此,如果表发生变化,则必须重新编译程序)。在需要考虑表大小的情况下,对文件进行标记也很有用。

我发现,当我做一个1:1的关系,它完全是一个系统的原因,而不是一个关系的原因。

例如,我发现将用户的保留方面放在一个表中,并将用户的可编辑字段放在另一个表中,这样可以更容易地在逻辑上编写关于这些字段的权限的规则。

但你是对的,在理论上,1:1的关系完全是人为的,而且几乎是一种现象。然而,从逻辑上讲,它允许程序和优化更容易地抽象数据库。

稀疏。从技术上讲,数据关系可能是1:1,但是并不需要为每一行都存在相应的行。因此,如果您有2000万行,并且有一组值只存在于其中的0.5% ,那么如果您将这些列推出到一个可以稀疏填充的表中,则可以节省大量空间。

我还可以考虑这样的情况: 在面向对象模型中使用继承,并且必须将继承树持久化到 DB。

例如,您有一个 Bird 和 Fish 类,它们都继承自 Animal。 在数据库中,可以有一个“ Animal”表,其中包含 Animal 类的公共字段,Animal 表与 Bird 表具有一对一关系,与 Fish 表具有一对一关系。

在这种情况下,您不必拥有一个 Animal 表,其中包含大量可为空的列来保存 Bird 和 Fish-properties,当记录表示一只鸟时,所有包含 Fish-data 的列都被设置为 NULL。

相反,Bird-table 中的记录与 Animal 表中的记录具有一对一的关系。

1:1的关系没有真正意义,如果您进入规范化,因为任何1:1的关系都将保存在同一个表中。

但在现实世界中,情况往往不同。您可能希望将数据分解为与应用程序接口匹配的数据。

大多数时候,设计被认为是1:1,直到有人问“好吧,为什么不能是1: 许多”?过早地将概念从另一个概念中分离出来是为了预测这个常见的场景。Person 和 Address 的绑定不是那么紧密。很多人都有多个地址。诸如此类。

通常两个独立的对象空间意味着一个或两个对象空间可以相乘(x: many)。如果两个对象真的,真的是1:1,甚至从哲学上来说,那么它更像是一种“是-关系”。这两个“对象”实际上是一个整体对象的一部分。

这也是一种扩展已经投入生产的表的方法,与“真正的”数据库更改相比,这种方法的风险(感知)更小。在遗留系统中看到1:1的关系通常是一个很好的指示器,表明字段是在初始设计之后添加的。

我使用它们主要有几个原因。一个是数据变化率的显著差异。我的一些表可能有审计跟踪记录的早期版本,如果我只关心跟踪10列中5列的早期版本,将这5列分割到一个单独的表中,并在其上设置审计跟踪机制,那么效率会更高。此外,我可能有只写的记录(比如一个会计应用程序)。你不能改变美元数额,或他们的帐户,如果你犯了一个错误,那么你需要做一个相应的记录,调整了不正确的记录,然后创建一个更正条目。我对表有一些约束,强制它们不能被更新或删除,但是我可能有一些对象的属性是可延展的,这些属性保存在一个单独的表中,没有修改的限制。我还有一次这么做是在医疗记录申请中。有些与访问相关的数据一旦签署就不能更改,还有些与访问相关的数据在签署后可以更改。在这种情况下,我将对数据进行分割,并在被锁定的表上放置一个触发器,在签名时拒绝对被锁定的表进行更新,但允许对医生未签名的数据进行更新。

另一个海报评论1:1没有正常化,我不同意在某些情况下,特别是子类型。假设我有一个员工表,主键是他们的 SSN (这是一个例子,让我们省去关于这个键是否适合其他线程的争论)。员工可以是不同的类型,比如临时的或永久的,如果他们是永久的,他们有更多的字段需要填写,比如办公室电话号码,如果类型 = “永久”,那么它应该不为空。在第三个普通表单数据库中,列应该只依赖于键,也就是雇员,但实际上它取决于雇员和类型,所以1:1的关系是完全正常的,在这种情况下是可取的。它还可以防止表过于稀疏,如果我有10列通常填充,但20个额外的列只适用于某些类型。

我能想到的最常见的场景是当你有 BLOB 的时候。假设您希望在数据库中存储大图像(通常,这不是存储大图像的最佳方式,但有时约束使其更加方便)。您通常希望 blob 位于单独的表中,以改进非 blob 数据的查找。

1-1关系也是必要的,如果你有太多的信息。表中的每个记录都有记录大小限制。有时将表分成两部分(主表中最常查询的信息) ,这样记录大小就不会太大。数据库在查询表是否狭窄时也更有效率。

在任何地方,两个完全独立的实体都有着一对一的关系,这样的例子肯定有很多:

人 <-> 牙医 (它的1: N,所以它是错的!)

人 <-> 医生 (它的1: N,所以它也是错的!)

人士 <-> 配偶 (1:0 | 1,所以大部分都错了!)

编辑: 是的,这些都是非常糟糕的例子,特别是如果我一直在寻找1:1,而不是0或1在任何一边。我猜是我的大脑出了问题: -)

所以,我再试一次。事实证明,经过一番思考之后,唯一可以让两个独立的实体(就软件而言)一直在一起的方法就是让它们以更高的分类方式共存。然后,当且仅当你跌入一个较低的分解,事物是而且应该是独立的,但在较高的层次上,它们不能没有彼此而生活。背景是关键。

对于医疗数据库,您可能希望存储关于身体特定区域的不同信息,并将它们作为单独的实体保存。在这种情况下,病人只有一个头,他们需要有它,否则他们就不是病人。(它们也有一个心脏,和许多其他必要的单个器官)。例如,如果您对跟踪手术感兴趣,那么每个区域应该是一个独特的单独实体。

在生产/库存系统中,如果您跟踪车辆的组装,那么您当然希望观察发动机的进度与车身不同,但是存在一对一的关系。护理必须有一个引擎,而且只有一个(否则它就不再是一辆“汽车”)。一台发动机只属于一辆汽车。

在每种情况下,您都可以将单独的实体作为一个大记录生成,但是考虑到分解的级别,这将是错误的。在这些特定的上下文中,它们是真正独立的实体,尽管它们在更高的层次上可能并非如此。

保罗。

你的问题可以有几种解释方式,因为你的措辞方式。

在现实世界中,数据项之间肯定存在1:1的关系。毫无疑问。“是一个”的关系通常是一对一的。汽车就是汽车。 一辆车就是一辆车。一辆车可能就是一辆车。有些车辆是卡车,在这种情况下,一辆车不是汽车。有几个答案解释了这种解释。

但我认为你真正想问的是... 当1:1的关系存在时,桌子应该被分开吗?换句话说,是否应该有两个包含完全相同键的表?在实践中,我们大多数人只分析主键,而不分析其他候选键,但这个问题略有不同。

1NF、2NF 和3NF 的规范化规则从来不需要将一个表分解(拆分)成具有相同主键的两个表。我还没有弄清楚是否在 BCNF、4NF 或5NF 中放置模式会导致具有相同键的两个表。在我看来,答案是否定的。

有一个称为6NF 的规范化级别。6NF 的规范化规则肯定会导致两个表具有相同的主键。6NF 优于5NF 的优点是可以完全避免 NULLS。这对一些(但不是全部)数据库设计人员来说很重要。我从未费心将模式放入6NF 中。

在6NF 中,缺少的数据可以用一个被省略的行来表示,而不是在某些列中使用 NULL 的行。

除了规范化以外,还有其他原因导致分割表。有时分割表会带来更好的性能。使用某些数据库引擎,可以通过对表进行分区而不是实际分割来获得同样的性能优势。这样做的好处是保持逻辑设计易于理解,同时为数据库引擎提供加快速度所需的工具。

在我看来,1:1关系映射了 RDBMS 上的类继承。 有一个表 A 包含公共属性,即父类状态 每个继承的类状态都映射到 RDBMS 上,表 B 具有1:1的关系 到一个表,其中包含专门的属性。 表 namend A 还包含一个表示“强制转换”功能的“ type”字段

马里奥

在 SQL 中,不可能在两个表之间强制执行1:1关系,而这种关系对于双方都是强制性的(除非这些表是只读的)。对于大多数实际用途来说,SQL 中的“1:1”关系实际上意味着1:0 | 1。

不能在引用约束中支持强制基数是 SQL 的严重限制之一。“可延迟”约束实际上并不算数,因为它们只是表示约束在某些时候没有得到强制执行的一种方式。

我所看到的1:1关系的最佳原因是数据库设计的 SuperType SubType。我根据这个模型创建了一个房地产 MLS 数据结构。有五个不同的数据源: 住宅,商业,多家庭,酒店和土地。

我创建了一个名为 SuperType 的属性,其中包含五个独立数据提要中每个提要所共有的数据。这允许跨所有数据类型进行非常快速的“简单”搜索。

我创建了五个独立的子类型,它们为五个数据提要中的每一个存储唯一的数据元素。每个 SuperType 记录与相应的 SubType 记录有1:1的关系。

如果客户想要一个详细的搜索,他们必须选择一个 Super-Sub 类型,例如 Properties。

如果数据库中有某种类型化的对象,则可以这样做。

比如在一个表 T1中,列 C1,C2,C3... 有一个一对一的关系。没关系,这是规范化的形式。现在假设在表 T2中,列 C1、 C2、 C3,... (名称可能不同,但假设类型和角色是相同的)也有一对一的关系。对于 T2来说没有问题,原因与 T1相同。

然而,在这种情况下,我认为适合单独的表 T3,其中包含 C1、 C2、 C3... 以及从 T1到 T3和从 T2到 T3的一对一关系。如果存在另一个表,其中已经存在一个1到多个 C1,C2,C3... ... 比如说从表 A 到表 B 中的多个行,那么,你使用 B 而不是 T3,并且从 T1到 B 有一个一对一的关系,从 T2到 B 也是一样的,从 A 到 B 仍然有一个到多个关系。

我相信规范化不同意这一点,这可能是它之外的一个想法: 识别对象类型并将相同类型的对象移动到它们自己的存储池中,使用某些表中的一对一关系,以及其他表中的一对多关系。

大多数排名靠前的答案为1:1关系提供了非常有用的数据库调优和优化理由,但是我只想关注“在野外”中自然出现1:1关系的例子。

请注意大多数示例的数据库实现的一个重要特征: 没有保留关于1:1关系的历史信息。也就是说,这些关系在任何给定的时间点都是1:1。如果数据库设计人员希望记录关系参与者随时间的变化,那么关系将变成1: M 或 M: M; 它们将失去1:1的特性。明白了这一点之后,我们开始:

  • “ Is-A”或超类型/子类型或继承/分类关系 : 这个类别是当一个实体是另一个实体的特定类型时。例如,可以有一个应用于所有雇员的属性的 Employee 实体,然后有不同的实体来指示具有该雇员类型特有属性的特定类型的雇员,例如医生、会计师、飞行员等。这种设计避免了多个 null,因为许多雇员没有特定子类型的专门属性。这个类别中的其他例子可以是 Product 作为超类型,ManufacturingProduct 和 MaintenanceSupply 作为子类型; Animal 作为超类型,Dog 和 Cat 作为子类型; 等等。请注意,无论何时您尝试将面向对象的继承层次结构映射到关系数据库(例如在对象-关系模型中) ,这种关系都代表了这种情况。

  • “老板”关系 ,如经理、主席、总裁等,其中一个组织单位只能有一个老板,一个人只能是一个组织单位的老板。如果这些规则适用,那么你就有了一个1:1的关系,比如一个部门的一个经理,一个公司的一个 CEO,等等。“老板”关系不仅仅适用于人。如果一家公司只有一家商店作为总部,或者只有一个城市是一个国家的首都,也会发生同样的关系。

  • 某些稀缺资源的分配,例如,一个员工一次只能分配一辆公司的汽车(例如,每个卡车司机一辆卡车,每个出租车司机一辆出租车,等等)。最近一位同事给我举了这个例子。

  • 婚姻(至少在一夫多妻制为非法的司法管辖区) : 一个人一次只能与另一个人结婚。我从一本教科书中得到了这个例子,当一家公司记录员工之间的婚姻时,这个例子就是1:1的一元关系。

  • 匹配保留 : 当一个独特的保留作为两个独立的实体提出并实现时。例如,汽车租赁系统可能在一个实体中记录预订,然后在另一个单独的实体中记录实际的租赁。虽然这种情况也可以设计为一个实体,但将这些实体分开可能是有意义的,因为并非所有的保留都得到履行,并非所有的租金都需要保留,而且这两种情况都很常见。

我重申我之前提出的警告,即只有在没有记录历史信息的情况下,这些关系才是1:1关系。因此,如果一个员工改变了他们在组织中的角色,或者一个经理负责不同的部门,或者一个员工被重新分配了一辆车,或者有人守寡并再婚,那么关系参与者就可以改变。如果数据库没有存储关于这些1:1关系的任何以前的历史记录,那么它们仍然是合法的1:1关系。但是如果数据库记录历史信息(比如为每个关系添加开始和结束日期) ,那么它们几乎都会转换成 M: M 关系。

历史记录有两个明显的例外: 首先,一些关系变化如此之少,以至于历史信息通常不会被存储。例如,大多数 IS-A 关系(例如产品类型)是不可变的; 也就是说,它们永远不会改变。因此,历史记录点是没有意义的; 这些将始终作为自然的1:1关系实现。其次,预订-租赁关系存储的日期是分开的,因为预订和租赁是独立的事件,每个事件都有自己的日期。由于实体有自己的日期,而不是1:1关系本身有一个开始日期,所以即使存储了历史信息,这些关系也将保持为1:1关系。

如果有任何显著的性能好处,您可以创建一对一的关系表。您可以将很少使用的字段放到单独的表中。

出于安全目的,它是不必要的,但是有更好的方法来执行安全检查。想象一下,你创造了一把只能打开一扇门的钥匙。如果钥匙可以打开任何其他的门,你应该按警铃。本质上,你可以有“公民表”和“投票表”。公民一票支持候选人一票,该票存储在投票表中。如果一个公民再次出现在投票表中,那么他们应该是一个警报。请注意,这是一个一对一的关系,因为我们不是指候选人字段,我们是指投票表和公民表。

例如:

 Citizen Table
id = 1, citizen_name = "EvryBod"
id = 2, citizen_name = "Lesly"
id = 3, citizen_name = "Wasserman"


Candidate Table
id = 1, citizen_id = 1, candidate_name = "Bern Nie"
id = 2, citizen_id = 2, candidate_name = "Bern Nie"
id = 3, citizen_id = 3, candidate_name = "Hill Arry"

那么,如果我们看到投票表是这样的:

 Voting Table
id = 1, citizen_id = 1, candidate_name = "Bern Nie"
id = 2, citizen_id = 2, candidate_name = "Bern Nie"
id = 3, citizen_id = 3, candidate_name = "Hill Arry"
id = 4, citizen_id = 3, candidate_name = "Hill Arry"
id = 5, citizen_id = 3, candidate_name = "Hill Arry"

我们可以说三号市民是一个骗子,一个骗了伯尔尼 · 聂的骗子,只是一个例子。

当您处理来自第三方产品的数据库时,您可能不希望为了防止紧密耦合而更改它们的数据库。但是你的数据可能与他们的数据1:1对应