有没有时间使用数据库1:1关系有意义?

时间:2009-02-05 19:08:43

标签: sql database-design one-to-one database-normalization

我前几天正在考虑规范化,我想到了,我想不出一个数据库中应该有1:1关系的时间。

名称:SSN?我把它们放在同一张桌子上 是PersonID:AddressID?同样,同桌。

我可以提出一个很多或者很多的例子:很多(有适当的中间表),但从不是1:1。

我错过了一些明显的东西吗?

26 个答案:

答案 0 :(得分:146)

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

作为逻辑分区的示例:您拥有有关员工的数据,但是当且仅当他们选择拥有健康保险时,才需要收集更多的数据。我会将有关健康保险的人口统计数据保存在不同的表格中,以便更轻松地进行安全分区,并避免在与保险无关的查询中拖运数据。

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

物理分区非常有用每当您的查询需要更大实体的一致子集时。

答案 1 :(得分:116)

一个原因是数据库效率。具有1:1关系允许您拆分在行/表锁定期间将受影响的字段。如果表A有大量更新,表b有大量读取(或者有来自另一个应用程序的大量更新),那么表A的锁定不会影响表B中发生的事情。

其他人提出了一个好点。安全性也可能是一个很好的理由,取决于应用程序等如何影响系统。我倾向于采取不同的方法,但它可以是限制对某些数据的访问的简单方法。在紧要关头拒绝访问某个表格真的很容易。

My blog entry about it.

答案 2 :(得分:40)

稀疏。数据关系在技术上可以是1:1,但是对于每一行不必存在相应的行。因此,如果你有两千万行并且有一些值只存在于其中的0.5%,那么如果将这些列推出到可以稀疏填充的表中,则节省的空间很大。

答案 3 :(得分:23)

大多数排名靠前的答案为1:1的关系提供了非常有用的数据库调优和优化原因,但我希望只关注"在野外"自然发生1:1关系的例子。

请注意大多数这些示例的数据库实现的一个重要特征:没有关于1:1关系的历史信息。也就是说,这些关系在任何给定的时间点都是1:1。如果数据库设计者想要记录关系参与者随时间的变化,则关系变为1:M或M:M;他们失去了1:1的性质。有了这个,这里有:

  • <强>&#34;是-A&#34;或超类型/子类型或继承/分类关系:此类别是指一个实体是另一个实体的特定类型。例如,可能有一个Employee实体,其属性适用于所有员工,然后是不同的实体,以指示具有该员工类型唯一属性的特定类型的员工,例如医生,会计师,飞行员等。这种设计避免了多个空值,因为许多员工不具备特定子类型的专用属性。此类别中的其他示例可以是Product as supertype,ManufacturingProduct和MaintenanceSupply作为子类型;动物为超类型,狗和猫为亚型;请注意,每当您尝试将面向对象的继承层次结构映射到关系数据库(例如在对象关系模型中)时,这就是表示此类场景的关系。

  • <强>&#34;首领&#34;关系,例如经理,主席,总裁等,组织单位只能有一个老板,一个人只能是一个组织单位的老板。如果这些规则适用,那么你就有1:1的关系,例如一个部门的一个经理,一个公司的一个CEO等等。&#34; Boss&#34;关系不仅适用于人。例如,如果只有一家商店作为公司的总部,或者只有一个城市是一个国家的首都,则会发生同样的关系。

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

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

  • 匹配预订:当进行唯一预订,然后作为两个单独的实体履行时。例如,汽车租赁系统可以在一个实体中记录预订,然后在单独的实体中记录实际的租赁。虽然这种情况可以替代地设计为一个实体,但是由于并非所有预订都得到满足,因此将实体分开可能是有意义的,并且并非所有租赁都需要预订,并且这两种情况都很常见。

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

历史记录有两个值得注意的例外:首先,一些关系变化很少,通常不会存储历史信息。例如,大多数IS-A关系(例如产品类型)是不可变的;也就是说,他们永远不会改变。因此,历史记录点没有实际意义;这些将始终作为自然的1:1关系实现。其次,预订 - 租赁关系商店的日期分开,因为预订和租赁是独立的活动,每个活动都有自己的日期。由于实体有自己的日期,而不是具有开始日期的1:1关系,即使存储了历史信息,这些关系仍将保持为1:1的关系。

答案 4 :(得分:21)

您的问题可以通过多种方式解释,因为您的措辞方式。答复显示了这一点。

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

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

1NF,2NF和3NF的规范化规则永远不需要将表分解(拆分)为具有相同主键的两个表。我还没有弄清楚,在BCNF,4NF或5NF中放置模式是否会导致两个具有相同密钥的表。在我的头脑中,我会猜测答案是否定的。

有一种称为6NF的标准化程度。 6NF的规范化规则肯定会导致两个表具有相同的主键。 6NF具有超过5NF的优势,可以完全避免NULLS。这对一些(但不是全部)数据库设计者很重要。我从来不打算将架构放入6NF。

在6NF中,缺失的数据可以由省略的行表示,而不是在某些列中具有NULL的行。

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

答案 5 :(得分:19)

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

另一张海报评论1:1没有被标准化,在某些情况下,我会不同意,尤其是亚型。假设我有一个员工表,主键是他们的SSN(这是一个例子,让我们保存关于这是否是另一个线程的好键的辩论)。员工可以是不同类型的,例如临时的或永久的,如果他们是永久性的,他们有更多的字段需要填写,例如办公室电话号码,如果类型='永久',则该字段应该不为空。在第三个普通形式的数据库中,该列应仅依赖于密钥,即雇员,但实际上它取决于员工和类型,因此1:1关系是完全正常的,在这种情况下是可取的。它还可以防止过度稀疏的表,如果我有10列通常填充,但只有20列用于某些类型。

答案 6 :(得分:13)

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

答案 7 :(得分:9)

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

在真实数据库中,将一个很少使用的字段保存在一个单独的表中有时很有用:使用此字段加速查询并且仅使用此字段;避免锁定等。

答案 8 :(得分:8)

我还可以想到你有一个使用继承的OO模型的情况,并且继承树必须保存到数据库。

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

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

相反,您在Birds-table中有一条与Animal表中的记录具有一对一关系的记录。

答案 9 :(得分:8)

除了使用视图来限制对字段的访问权限之外,有时将受限字段保留在只有某些用户才能访问的单独表中是有意义的。

答案 10 :(得分:8)

如果你有太多的信息,也需要1-1关系。表中的每条记录都有记录大小限制。有时表被分成两部分(主表中最常查询的信息),只是为了使记录大小不会太大。如果表格很窄,数据库在查询时也更有效。

答案 11 :(得分:5)

这也是一种扩展已经投入生产的表的方法,其风险比“实际”数据库更改更少(感知)。在遗留系统中看到1:1关系通常是在初始设计之后添加字段的良好指标。

答案 12 :(得分:5)

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

无法在引用约束中支持强制基数是SQL严重限制之一。 “可延迟”约束并不真正重要,因为它们只是说某种程度上不强制执行约束的方式。

答案 13 :(得分:4)

大多数时候,设计被认为是1:1,直到有人问“好,为什么不能1:多”?在预期这种常见情况下,过早地将概念彼此分开。人和地址不紧密绑定。很多人都有多个地址。等等...

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

答案 14 :(得分:4)

我发现,当我以1:1的比例完成一个系统性的原因,而不是关系的原因。

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

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

答案 15 :(得分:4)

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

答案 16 :(得分:3)

仅在某些情况下需要的扩展信息。在遗留应用程序和编程语言(如RPG)中,程序是在表上编译的(因此,如果表更改,则必须重新编译程序)。在您不得不担心表格大小的情况下,沿文件标记也很有用。

答案 17 :(得分:2)

最常见的是它更像是一种物理而非逻辑结构。它通常用于对表进行垂直分区,以利用跨物理设备拆分I / O或与隔离较少访问的数据相关联的其他查询优化或需要保持比同一对象上的其他属性更安全的数据(SSN,薪水等)。

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

答案 18 :(得分:2)

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

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

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

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

答案 19 :(得分:1)

在我看来,1:1关系在RDBMS上映射类继承。 有一个表A包含公共属性,即partent类状态 每个继承的类状态都映射在RDBMS上,表B与1:1的关系 到包含专用属性的表。 表名A还包含表示“强制转换”功能的“类型”字段

再见 马里奥

答案 20 :(得分:0)

1:1关系并不真正有意义,如果你进入规范化,因为任何1:1将被保存在同一个表中。

但在现实世界中,它往往是不同的。您可能希望将数据中断以匹配应用程序界面。

答案 21 :(得分:0)

如果您的数据库中有某种类型的对象,可能会发生。

在表格中说T1,你有列C1,C2,C3 ......与一对一的关系。没关系,它是标准化的形式。现在在表T2中说,你有列C1,C2,C3,...(名称可能不同,但说类型和角色是相同的),也有一对一的关系。由于与T1相同的原因,T2没问题。

然而,在这种情况下,我看到适合单独的表T3,保持C1,C2,C3 ......以及从T1到T3以及从T2到T3的一对一关系。如果存在另一个表格,我甚至会看到一个合适的表格,其中已经存在一个到多个C1,C2,C3 ...从表A到表B中的多个行。然后,代替T3,你使用B,并且从T1到B的一对一关系,从T2到B的关系是一对一的,从A到B的关系仍然是一对多关系。

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

答案 22 :(得分:0)

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

答案 23 :(得分:0)

出于安全考虑,这是不必要的,但有更好的方法来执行安全检查。想象一下,你创造了一个只能打开一扇门的钥匙。如果钥匙可以打开任何其他门,您应该响铃。从本质上讲,你可以拥有&#34; CitizenTable&#34;和&#34;投票表&#34;。 Citizen One投票给Candidate One,存储在投票表中。如果公民一次再次出现在投票表中,那么他们应该是一个警报。建议,这是一对一的关系,因为我们没有引用候选字段,我们正在引用投票表和公民表。

示例:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, VirtualTrees;

type
  TEditorAction = (eaCancel, eaAccept, eaNotSet);

  TForm1 = class(TForm)
    vstTree: TVirtualStringTree;
    procedure vstTreeCreateEditor(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink);
    procedure DoWatchTreeEditorTimer(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FEndEditTimer: TTimer;
    FEditorAction: TEditorAction;
    procedure SetEditorAction(const Value: TEditorAction);
  public
    property EditorAction: TEditorAction read FEditorAction write SetEditorAction;
  end;

  TPropertyEdit = class(TInterfacedObject, IVTEditLink)
    procedure EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  private
    FEdit: TWinControl;
    FTree: TVirtualStringTree;
    FNode: PVirtualNode;
    FColumn: Integer;
  public
    FForm: TForm1;
    destructor Destroy; override;
    function BeginEdit: Boolean; stdcall;
    function CancelEdit: Boolean; stdcall;
    function EndEdit: Boolean; stdcall;
    function GetBounds: TRect; stdcall;
    function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
    procedure ProcessMessage(var Message: TMessage); stdcall;
    procedure SetBounds(R: TRect); stdcall;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  FEndEditTimer := TTimer.Create(nil);
  FEndEditTimer.Enabled := False;
  FEndEditTimer.Interval := 100;
  FEndEditTimer.OnTimer := DoWatchTreeEditorTimer;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FEndEditTimer);
end;

procedure TForm1.vstTreeCreateEditor(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink);
begin
  EditLink := TPropertyEdit.Create;
  TPropertyEdit(EditLink).FForm := Self; { lets us signal the form when the editor needs to be destroyed }
  FEditorAction := eaNotSet;
end;

procedure TForm1.SetEditorAction(const Value: TEditorAction);
begin
  if FEditorAction <> Value then
  begin
    FEditorAction := Value;
    FEndEditTimer.Enabled := True;
  end;
end;

procedure TForm1.DoWatchTreeEditorTimer(Sender: TObject);
begin
  FEndEditTimer.Enabled := False;
  Application.ProcessMessages;
  case FEditorAction of
    eaCancel:
      begin
        vstTree.CancelEditNode;
        vstTree.SetFocus;
      end;
    eaAccept:
      begin
        vstTree.EndEditNode;
        vstTree.SetFocus;
      end;
  end;
end;

{ TPropertyEdit }

function TPropertyEdit.BeginEdit: Boolean;
begin
  Result := True;
  FEdit.Show;
end;

function TPropertyEdit.CancelEdit: Boolean;
begin
  Result := True;
  FEdit.Hide;
  FForm.FEditorAction := eaCancel;
end;

destructor TPropertyEdit.Destroy;
begin
  if FEdit <> nil then
    FreeAndNil(FEdit);
  inherited;
end;

procedure TPropertyEdit.EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  case Key of
    VK_ESCAPE:
      begin
        Key := Word(0); { some versions of Delphi throw random A/V errors if '0' is not cast as a Word() }
        FForm.EditorAction := eaCancel;
      end;
    VK_RETURN:
      begin
        Key := Word(0); { some versions of Delphi throw random A/V errors if '0' is not cast as a Word() }
        FForm.EditorAction := eaAccept
      end;
  end;
end;

function TPropertyEdit.EndEdit: Boolean;
begin
  Result := True;
  { Do something with the value provided by the user }
  FEdit.Hide;
  FForm.EditorAction := eaAccept;
end;

function TPropertyEdit.GetBounds: TRect;
begin
  Result := FEdit.BoundsRect;
end;

function TPropertyEdit.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean;
begin
  Result := True;
  FTree := Tree as TVirtualStringTree;
  FNode := Node;
  FColumn := Column;
  { Setup the editor for user }
  FEdit := TSomeWinControl.Create(nil);
  FEdit.Properties := Values;
  { Capture keystrokes }
  FEdit.OnKeyDown := EditKeyDown;
end;

procedure TPropertyEdit.ProcessMessage(var Message: TMessage);
begin
  FEdit.WindowProc(Message);
end;

procedure TPropertyEdit.SetBounds(R: TRect);
var
  Dummy: Integer;
begin
  FTree.Header.Columns.GetColumnBounds(FColumn, Dummy, R.Right);
  FEdit.BoundsRect := R;
end;

end.

然后,如果我们看到投票表如此:

 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"

我们可以说3号公民是骗了Bern Nie的骗子裤子。只是一个例子。

答案 24 :(得分:0)

当您使用第三方产品的数据库时,您可能不想更改其数据库以防止紧密耦合。但是您的数据可能与他们的数据1:1相对应

答案 25 :(得分:-4)

任何地方都有两个完全独立的实体共享一对一的关系。必须有很多例子:

person&lt; - &gt;牙医(它的1:N,所以它错了!)

person&lt; - &gt;医生(它的1:N,所以它也错了!)

person&lt; - &gt;配偶(它的1:0 | 1,所以它大多错了!)

编辑:是的,这些都是非常糟糕的例子,特别是如果我一直在寻找1:1,而不是任何一方的0或1。我想我的大脑是错误的: - )

所以,我会再试一次。经过一番思考后,事实证明,你可以拥有两个独立实体的唯一方法(就软件而言)所有时间都在一起就是它们在更高的分类中一起存在。然后,当且仅当你陷入较低的分解时,事物应该是分开的,但在更高的层次上它们不能没有彼此。语境,然后是关键。

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

在生产/库存系统中,如果您正在跟踪车辆的装配,那么您当然希望以不同于车身的方式观察发动机的进度,但是存在一对一的关系。护理必须有一个引擎,只有一个(或它不再是'汽车')。发动机只属于一辆汽车。

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

保罗。