NULL和NULL应该与FK关系一起表示什么 - 数据库

时间:2009-03-18 00:06:30

标签: sql database-design null

我在关系型SQL数据库中创建FK关系时遇到了困难,经过工作中的简短讨论后,我们意识到我们有可空列,最有可能导致问题。我总是将NULL视为未分配,未指定,空白等等,并且确实从未见过这样的问题。

我正在与之交谈的其他开发人员认为,处理两个实体之间确实存在关系的情况的唯一方法是,您必须创建一个连接两个实体的数据的表...

至少对于我来说,对于包含来自另一个表的ID的列来说,如果该列不为null,那么它似乎很直观,那么它必须具有来自另一个表的ID,但如果它是NULL,那么这是好的,继续前进。看起来这本身就与某些人所说的和建议相矛盾。

处理两个表之间可能存在关系的情况的最佳实践或正确方法是什么?如果指定了值,那么它必须位于另一个表中...

12 个答案:

答案 0 :(得分:9)

这是完全可以接受的,这意味着,如果该列具有任何值,则其值必须存在于另一个表中。 (我看到其他答案断言否则,但我不同意。)

想一下车辆和发动机的表格,并且发动机尚未安装在车辆中(因此VehicleID为空)。或者是一个带有主管专栏和公司首席执行官的员工表。

更新:根据Solberg的请求,下面是两个具有外键关系的表的示例,表明外键字段值可以为空。

CREATE TABLE [dbo].[EngineTable](
    [EngineID] [int] IDENTITY(1,1) NOT NULL,
    [EngineCylinders] smallint NOT NULL,
 CONSTRAINT [EngineTbl_PK] PRIMARY KEY NONCLUSTERED 
(
    [EngineID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE [dbo].[CarTable](
    [CarID] [int] IDENTITY(1,1) NOT NULL,
    [Model] [varchar](32) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
    [EngineID] [int] NULL
 CONSTRAINT [PK_UnitList] PRIMARY KEY CLUSTERED 
(
    [CarID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[CarTable]  WITH CHECK ADD CONSTRAINT [FK_Engine_Car] FOREIGN KEY([EngineID])
REFERENCES [dbo].[EngineTable] ([EngineID])


Insert Into EngineTable (EngineCylinders) Values (4);
Insert Into EngineTable (EngineCylinders) Values (6);
Insert Into EngineTable (EngineCylinders) Values (6);
Insert Into EngineTable (EngineCylinders) Values (8);

- 现在有些测试:

Insert Into CarTable (Model, EngineID) Values ('G35x', 3);  -- References the third engine

Insert Into CarTable (Model, EngineID) Values ('Sienna', 13);  -- Invalid FK reference - throws an error

Insert Into CarTable (Model) Values ('M');  -- Leaves null in the engine id field & does NOT throw an error 

答案 1 :(得分:8)

我认为这场辩论是object-relational impedence mismatch的另一个副产品。基于对关系代数语义的一些更深入的理解,一些DBA类型会迂腐地说永远不允许在FK中使用null,但是应用程序开发人员会认为它使得它们的领域层更加优雅。

“尚未建立”关系的用例是有效的,但是对于空FK,有些人发现它通过引入更复杂的SQL特性,特别是LEFT JOIN来增加查询的复杂性。

我见过的一个常见的替代解决方案是在每个表中引入一个“空行”或“哨兵行”,其中pk = 0或pk = 1(基于您的RDBMS支持的内容)。这允许您设计具有“尚未建立”关系的域层,但也避免引入LEFT JOIN,因为您保证总会有某些东西要加入。

当然,这种方法也需要尽职尽责,因为你基本上要关闭LEFT JOIN,因为你必须在查询中检查你的哨兵行的存在,这样你就不会更新/删除它等等。是否需要权衡利弊有理由是另一回事。我倾向于同意重新发明null只是为了避免一个更高级的加入似乎有点傻,但我也在一个应用程序开发人员没有赢得DBA辩论的环境中工作。

<强>编辑

我删除了一些“事实问题”的措辞,并试图澄清“失败”连接的含义。 @ wcoenen的例子是我个人经常听到的避免空FK的原因。并不是因为他们在“破碎”中失败,而是失败 - 有些人会争辩 - 坚持最不惊讶的原则。

另外,我把这个回复变成了一个wiki,因为我基本上把它从原来的状态中剔除了,并借用了其他帖子。

答案 2 :(得分:7)

我强烈支持外键中NULL的参数,以指示OLTP系统中的无父级,但在决策支持系统中,它很少能正常工作。在那里,最合适的做法是使用一个特殊的“不适用”(或类似)值作为子记录(在事实表中)可以链接到的父(在维度表中)。

这样做的原因是,向下钻取/跨越等的探索性质可能导致用户无法理解度量标准在仅仅询问有关它的更多信息时如何变化。例如,如果财务数据集市包含产品销售和其他收入来源的混合,那么深入到“产品类型”应该将非产品销售相关数据分类,而不是让这些数字从报告中删除,因为从事实表到产品维度表没有连接。

答案 3 :(得分:6)

当外键是复合时,会出现在外键列中允许空值的问题。如果两列中的一列为空,这意味着什么?另一列是否必须匹配引用表中的任何内容?使用简单(单列)外键约束,您可以使用空值。

另一方面,如果两个表之间的关系是有条件的(两个实体本身可以存在,但可能几乎巧合地相关)那么最好用“连接表”对表进行建模 - 表它包含引用表的FK和引用表的另一个,并且它有自己的主键作为两个FK的组合。

作为连接表的示例,假设您的数据库包含俱乐部和人员的表。有些人属于一些俱乐部。加入表将是club_members并且将包含引用“人员”表的人的FK,并且将包含该人所属的俱乐部的另一个FK,并且人和俱乐部的标识符的组合将是主要关键字。加入表。 (加入表的另一个名称是'关联'或'关联'表。)

答案 4 :(得分:4)

我倾向于设计一个传达该列意义的设计。就域而言,null可能意味着任何数量的事物。在相关表中添加一个“不需要”或“未选择”的值至少可以传达目的,而无需询问开发人员或查阅文档。

答案 5 :(得分:3)

假设您需要生成所有客户的报告。每个客户都有一个国家的FK,国家数据需要包含在报告中。现在假设您允许FK为null,并执行以下查询:

SELECT * FROM customer, country WHERE customer.countryID = country.ID

国家/地区FK为null的任何客户都会在报告中默默省略(您需要使用LEFT JOIN来修复它)。我发现这不直观且令人惊讶,所以我不喜欢NULL FK并在我的数据库模式中避免使用它们。相反,我使用哨兵值,例如一个特殊的“未知国家”。

答案 6 :(得分:3)

CREATE TABLE [tree]
{
    [id] int NOT NULL,
    [parent_id] int NULL
};

ALTER TABLE [tree] ADD CONSTRAINT [FK_tree_tree] FOREIGN KEY([parent_id])
REFERENCES [tree] ([id]);

这没有错!根节点将永远具有NULL父节点,这不是“尚未建立”关系的情况。加入这里也没问题。

让根节点指向自己作为父节点以避免使用NULL FK或任何其他创造性的解决方法,这意味着现实世界不再在数​​据库中准确建模。

没有人提到的一个潜在问题是包含大量NULL值的列的索引性能。但这本身与外键问题无关,但它可以使连接表现不佳。

我确实理解如果你是一个DBA,使用拥有数亿行的超大型数据库,你就不会想要NULL外键,因为它们根本就不能执行。但事实是,大多数开发人员在其生命周期中永远不会使用如此庞大的数据库,而今天的数据库可以处理这种情况,只有几十万行。为了强调一个(差)比喻,我们大多数人都没有驾驶F1赛车,而我妻子雅阁的自动变速器做了它需要做的事情就好了(或者至少,它曾经用过,直到它几周前破了...)。

答案 7 :(得分:3)

如果要为业务原因分配NULL,那么您实际上是在重新定义域中的NULL含义,并且必须为用户和未来的开发人员记录。如果存在将NULL作为外键的业务原因,那么我建议您按照其他人提到的那样做,并添加一个具有“N / A”或“未分配”行的值的连接记录。 / p>

当数据库中的NULL现在变为多重含义(业务含义,错误或未正确输入)时,可能会出现并发症,这可能导致问题更难以追踪。

答案 8 :(得分:2)

如果字段可以为空,我看不到空值的问题。如果该字段中存在信息,则滥用允许空值。

答案 9 :(得分:2)

你做对了。对于FK,NULL表示没有值(表示没有关系)。如果FK中有一个值,它必须恰好匹配它引用的PK中的一个值。

允许这样做并不一定是糟糕的设计。如果关系是一对多且是可选的,那么在一侧向表添加FK是完全可以的,在多方面引用PK。

如果一个关系是多对多的,那么它需要一个自己的表,称为联结表。该表有两个FK,每个FK引用一个相关表中的PK。在这种情况下,可以通过简单地省略联结表中的整行来表示省略的关系。

有些人的设计是为了避免允许NULLS的必要性。这些人将使用联结表进行多对一关系,并在省略关系时省略一行,如上所述。

我自己并不遵循这种做法,但它确实有一定的好处。

答案 10 :(得分:2)

我不得不说,尽管显然可能,但根据Jonathon Leffler的精心设计,使用连接表有什么问题呢?

我遇到了这个问题,因为我有完全相同的需求,但我的设计现在显着“更干净”,有一个连接表。我的数据库图现在清楚地向我显示我的字段是可选的,从模式POV可以很好地适用于我。

然后为了简化我的查询,我只是创建了一个视图LEFT将两个表连接在一起,这给出了可选连接的外观,但实际上使用了更清晰的数据库结构。在我的视图中也使用ISNULL(MyField,'None')我可以提供“不存在”的额外行设计的好处,但没有痛苦。

鉴于这里提到的要点,我与DBA在这一点上 - 为什么有一个空列,当你可以更容易使用视图时更“稳固”的关系?而且没有真正的额外努力。

答案 11 :(得分:0)

联接表是正确的方法。

键中的空值表示数据库设计错误。

空值未分配/空/空/等,它缺少/未知数据。

在外键字段中使用空值并不意味着“没有关系”,这意味着“我不知道是否存在关系” - 这显然是不好的。