有一种观点认为关系数据库中不应允许空值。也就是说,表的属性(列)不应允许空值。来自软件开发背景,我真的不明白这一点。似乎如果 null 在属性的上下文中是有效的,那么它应该被允许。这在对象引用通常为空的 Java 中很常见。没有丰富的数据库经验,我想知道我是否在这里遗漏了一些东西。
33 回答
从数据库规范化的角度来看,空值是负面的。这个想法是,如果一个值可以什么都不是,那么你真的应该将它拆分到另一个稀疏表中,这样你就不需要为没有值的项目提供行。
这是为了确保所有数据都是有效和有价值的。
但是,在某些情况下,具有空字段很有用,尤其是当您出于性能原因想要避免再次连接时(尽管如果数据库引擎设置正确,这应该不是问题,除非在非常高性能的情况下。)
-亚当
反对空值的一个论点是它们没有明确定义的解释。如果字段为空,则可以解释为以下任何一种:
- 值为“无”或“空集”
- 没有对该字段有意义的值。
- 值未知。
- 该值尚未输入。
- 该值是一个空字符串(对于不区分空字符串和空字符串的数据库)。
- 一些特定于应用程序的含义(例如,“如果值为空,则使用默认值。”)
- 发生了错误,导致该字段实际上不应该具有空值。
一些模式设计者要求所有值和数据类型都应该有明确定义的解释,因此空值是不好的。
这取决于。
只要您了解为什么允许NULL
s 在数据库中(需要在每列的基础上做出选择)以及您将如何解释、忽略或以其他方式处理它们,它们就可以了。
例如,像这样的列NUM_CHILDREN
- 如果你不知道答案,你会怎么做 - 它应该是NULL
. 在我看来,对于此列的设计,没有其他最佳选择(即使您有一个标志来确定该NUM_CHILDREN
列是否有效,您仍然必须在该列中有一个值)。
另一方面,如果您不允许NULL
s 并且在某些情况下具有特殊的保留值(而不是标志),例如 -1 表示真正未知的孩子数量,您必须以类似的方式解决这些问题,在公约、文件等条款。
因此,最终,这些问题必须通过约定、文档和一致性来解决。
正如亚当戴维斯在上述答案中显然支持的那样,将列标准化为稀疏(或不那么稀疏,在NUM_CHILDREN
示例或大多数数据具有已知值的任何示例的情况下)表的替代方案,同时能够消除所有 NULL,在一般实践中是不可行的。
在许多属性未知的情况下,为每一列连接到另一个表几乎没有意义,这可以让NULL
s 在更简单的设计中允许。连接的开销,主键的空间要求在现实世界中几乎没有意义。
这让人想起通过添加基数列可以消除重复行的方式,同时它在理论上解决了没有唯一键的问题,实际上这有时是不可能的 - 例如,在大规模数据中。然后,纯粹主义者很快建议使用代理 PK,但是从关系理论的角度来看,无意义的代理可以构成关系(表)中元组(行)的一部分的想法是可笑的。
空标记很好。真的,他们是。
对使用 NULL 有几种不同的反对意见。一些反对意见是基于数据库理论的。从理论上讲,理论与实践没有区别。在实践中,有。
确实,完全规范化的数据库完全可以在没有 NULLS 的情况下运行。任何必须省略数据值的地方都是可以省略整行而不会丢失信息的地方。
在实践中,将表分解到这种程度并没有多大用处,而且对数据库执行简单 CRUD 操作所需的编程变得更加乏味且容易出错,而不是更少。
在某些地方使用 NULLS 可能会导致问题:基本上这些都围绕以下问题:缺失数据的真正含义是什么?NULL 真正传达的只是给定字段中没有存储值。但是应用程序程序员从丢失的数据中得出的推论有时是不正确的,这会导致很多问题。
由于各种原因,数据可能会从某个位置丢失。这里有几个:
数据在这种情况下不适用。例如,单身人士的配偶名字。
数据输入表单的用户将字段留空,并且应用程序不需要在该字段中输入。
数据从某个其他数据库或文件复制到数据库,并且源中缺少数据。
在外键中编码了一个可选的关系。
一个空字符串存储在 Oracle 数据库中。
以下是有关何时避免 NULLS 的一些准则:
如果在正常的预期编程过程中,查询编写者必须编写大量 ISNULL、NV、COALESCE 或类似代码才能用有效值替换 NULL。有时,最好在存储时进行替换,前提是存储的是“现实”。
如果计数可能因为包含 NULL 的行被计数而关闭。通常,只需选择 count(MyField) 而不是 count(*) 就可以避免这种情况。
这里有一个地方可以让你更好地习惯 NULL,并相应地编程:每当你开始使用外连接时,比如 LEFT JOIN 和 RIGHT JOIN。与内部联接不同,外部联接背后的全部要点是在缺少某些匹配数据时获取行。缺失的数据将作为 NULLS 给出。
我的底线:不理解理论就不要放弃它。但是要学习何时偏离理论以及如何遵循它。
对数据字段使用 NULL 没有任何问题。将键设置为空时必须小心。主键永远不应为 NULL。外键可以为空,但您必须小心不要创建孤立记录。
如果某些东西“不存在”,那么您应该使用 NULL 而不是空字符串或其他类型的标志。
而不是写出所有关于 NULL 的问题,以及三态与布尔逻辑等 - 我将提供这个精辟的建议:
不要在列中使用 NULL,直到您发现自己添加了一个神奇的值来表示丢失或不完整的数据。
既然您要问这个问题,您应该非常小心处理 NULL 的方式。它有很多不明显的陷阱。如有疑问,请勿使用 NULL。
使用“N/A”或“N/K”或空字符串还有另一种选择 - 一个单独的表。
例如,如果我们可能知道也可能不知道客户的电话号码:
CREATE TABLE Customer (ID int PRIMARY KEY, Name varchar(100) NOT NULL, Address varchar(200) NOT NULL);
CREATE TABLE CustomerPhone (ID int PRIMARY KEY, Phone varchar(20) NOT NULL, CONSTRAINT FK_CustomerPhone_Customer FOREIGN KEY (ID) REFERENCES Customer (ID));
如果我们不知道电话号码,我们就不会在第二个表中添加一行。
不要低估通过将字段设为 NULLable 所创建的复杂性。例如,下面的 where 子句看起来会匹配所有行(位只能是 1 或 0,对吗?)
where bitfield in (1,0)
但是如果位域是 NULLable,它会遗漏一些。或进行以下查询:
select * from mytable
where id not in (select id from excludetable)
现在,如果 excludetable 包含 null 和 1,则转换为:
select * from mytable
where id <> NULL and id <> 1
但是“id <> NULL”对于任何 id 值都是错误的,所以这永远不会返回任何行。这甚至让经验丰富的数据库开发人员感到惊讶。
鉴于大多数人可能会被 NULL 吓到,我尽量避免它。
我会说绝对应该使用 Null。没有其他正确的方式来表示缺乏数据。例如,使用空字符串表示缺失的地址行是错误的,或者使用 0 表示缺失的年龄数据项是错误的。因为空字符串和 0 都是数据。Null 是表示这种情况的最佳方式。
这是一大堆蠕虫,因为 NULL 可能意味着很多事情:
- 没有死亡日期,因为这个人还活着。
- 没有手机号码,因为我们不知道它是什么,甚至不知道它是否存在。
- 没有社会安全号码,因为知道那个人没有。
其中一些可以通过规范化来避免,其中一些可以通过在该列中存在一个值(“N/A”)来避免,其中一些可以通过有一个单独的列来解释 NULL 的存在来减轻(“N/K”、“N/A”等)。
它也是一罐蠕虫,因为找到它们所需的 SQL 语法与非空值不同,很难加入它们,而且它们通常不包含在索引条目中。
由于前一个原因,您会发现 null 不可避免的情况。
由于后一个原因,您仍然应该尽力减少它们的数量。
无论如何,始终使用 NOT NULL 约束来防止需要值的空值。
空值的主要问题是它们具有特殊的语义,可以通过比较、聚合和连接产生意想不到的结果。
没有什么等于 null,也没有什么不等于、大于或小于 null,因此如果要进行任何批量比较,必须将 null 设置为占位符值。
这也是可能在连接中使用的复合键的问题。如果自然键包含可为空的列,您可能需要考虑使用合成键。
Null 可能会丢失计数,这可能不是您想要的语义。
可以连接的列中的空值将消除内部连接中的行。一般来说,这可能是所期望的行为,但它可能会为进行报道的人设置大象陷阱。
空值还有很多其他的微妙之处。Joe Celko为 Smarties 编写的 SQL有一整章关于这个主题,是一本好书,无论如何都值得一读。null 是一个很好的解决方案的一些例子是:
连接实体可能存在或不存在的可选关系。Null 是在外键列上表示可选关系的唯一方法。
您可能希望用于 null 以退出计数的列。
可能存在或不存在的可选数字(例如货币)值。数字系统中的“未记录”没有有效的占位符值(特别是在零是合法值的情况下),因此 null 确实是唯一的好方法。
您可能希望避免使用空值的一些示例,因为它们可能会导致细微的错误。
带有参考表的 FK 的代码字段上的“未记录”值。使用占位符值,因此您(或一些随机的业务分析师)在对数据库进行查询时不会无意中将行从结果集中删除。
没有输入任何内容的描述字段 - 空字符串 (
''
) 可以正常工作。这样就不必将空值视为特殊情况。报告或数据仓库系统上的可选列。对于这种情况,请在维度中为“未记录”创建一个占位符行并与之结合。这简化了查询并与临时报告工具很好地配合使用。
再一次,Celko 的书很好地处理了这个主题。
关于范式的最好的事情是它们是指南,不应该顽固地遵守指南。当学术界的世界与现实世界发生冲突时,你很少会发现许多幸存的学术界战士。
这个问题的答案是可以使用空值。如果您觉得空值与实际值的比率过高,只需评估您的情况并决定是否希望它们显示在表中或将数据折叠到另一个相关表中。
正如朋友喜欢说的,“不要让完美成为美好的敌人”。想想伏尔泰也这么说。8)
根据严格的关系代数,不需要空值。但是,对于任何实际项目,它们都是必需的。
首先,许多真实世界的数据是未知的或不适用的,并且 null 很好地实现了该行为。其次,它们使视图和外部连接更加实用。
我同意上面的许多答案,并且还相信 NULL 可以在适当的情况下用于规范化模式设计 - 特别是在您可能希望避免使用某种“幻数”或默认值的情况下,反过来,可以误导!
最终,我认为 null 的使用需要经过深思熟虑(而不是默认情况下),以避免上面答案中列出的一些假设,特别是在 NULL 可能被假定为“无”或“空”、“未知”的情况下'或'尚未输入值'。
null 表示没有值,而 0 没有,如果你看到一个 0 你不知道它的含义,如果你看到一个 null 你知道它是一个缺失值
我认为空值更清晰,0 和 '' 令人困惑,因为它们没有清楚地显示存储值的意图
您会发现分步数据采集系统无法避免在数据库中出现空值,因为提问/数据收集的顺序很少与逻辑数据模型相匹配。
或者您可以默认值(需要代码来处理这些默认值)。您可以假设所有字符串都是空的而不是 null,例如,在您的模型中。
或者,您可以使用暂存数据库表进行数据采集,直到获得所有数据,然后再填充实际的数据库表。这是很多额外的工作。
对于数据库,null 转换为“我没有这个值”。这意味着(有趣的是)允许空值的布尔列是完全可以接受的,并且出现在许多数据库模式中。相反,如果你的代码中有一个布尔值,它的值可以是“真”、“假”或“未定义”,那么你很可能会看到你的代码迟早会出现在 thedailywtf 上:)
所以是的,如果您需要允许字段根本没有任何值的可能性,那么在列上允许空值是完全可以接受的。它明显优于潜在的替代品(空字符串、零等)
Null 可能很难使用,但在某些情况下它们是有意义的。
假设您有一个带有“PaidDate”列的发票表,该列具有日期值。在发票付款之前,您在该栏中输入了什么(假设您事先不知道何时付款)?它不能是空字符串,因为那不是有效的日期。给它一个任意日期(例如 1/1/1900)是没有意义的,因为该日期根本不正确。似乎唯一合理的值是 NULL,因为它没有值。
在数据库中使用空值有一些挑战,但数据库可以很好地处理它们。真正的问题是当您将数据库中的空值加载到应用程序代码中时。这就是我发现事情变得更加困难的地方。例如,在 .NET 中,强类型数据集中的日期(模仿您的 DB 结构)是一种值类型,不能为空。因此,您必须构建解决方法。
尽可能避免使用空值,但不要排除它们,因为它们具有有效用途。
我认为您将概念数据建模与物理数据建模混淆了。
在 CDM 中,如果一个对象有一个可选字段,您应该子类型该对象并在该字段不为空时创建一个新对象。这就是 CDM 中的理论
在物理世界中,我们为现实世界做出了各种妥协。在现实世界中,NULL 非常好,它们是必不可少的
虽然从技术上讲,NULL 作为字段值是可以的,但它们经常不受欢迎。根据数据写入数据库的方式,有可能(并且常见)在字段中以空字符串值结束,而不是 NULL。因此,任何将此字段作为 WHERE 子句一部分的查询都需要处理这两种情况,这两种情况都是不必要的击键。
空岩石。如果在某些情况下不需要,SQL 将不会使用 IS NULL 和 IS NOT NULL 作为特例运算符。NULL 是概念普遍性的根源,其他一切都不是 NULL。只要数据值可能不存在但不会遗漏,就可以自由使用 NULL。如果默认值始终绝对正确,则默认值只能补偿 NULL。例如,如果我有一个单比特字段“IsReady”,则该字段具有默认值 false 并且不允许使用 NULL 可能非常有意义,但这隐含地断言我们知道什么都没有准备好,而事实上我们可能没有这样的知识。在工作流场景中,决定是否准备好的人可能还没有机会发表他们的意见,因此默认为 false 实际上可能是危险的,导致他们忽略似乎有的决定已制作,但实际上只是默认了。
顺便说一句,关于中间名首字母的例子,我父亲没有中间名,因此他的中间名首字母将为 NULL - 不是空白、空格或星号 - 除了在军队中,他的中间名首字母是 NMI = No Middle最初的。那有多傻?
从技术上讲,空值在关系数据库所基于的关系数学中是非法的。所以从纯粹的技术、语义关系模型的角度来看,不,它们是不行的。
在现实世界中,非规范化和一些违反模型的行为是可以的。但是,一般来说,空值是您应该更仔细地查看整体设计的一个指标。
我总是对空值非常警惕,并尽可能将它们标准化。但这并不意味着它们有时不是最佳选择。但我肯定会倾向于“无空值”,除非你真的确定在你的特定基础中拥有空值更好。
如果您使用的是 Oracle 数据库,则会遇到一个问题。如果您将空字符串保存到 CHAR 类型列,那么 Oracle 将强制该值为 NULL 而无需询问。因此,在 Oracle 的字符串列中避免 NULL 值是相当困难的。
如果您使用 NULL 值,请学习使用 SQL 命令 COALESCE,尤其是字符串值。然后,您可以防止 NULL 值传播到您的编程语言中。例如,假设一个人有 FirstName、MiddleName 和 FamilyName,但您想返回一个字段;
SELECT FullName = COALESCE(FirstName + ' ', '') + COALESCE(MiddleName+ ' ', '') + COALESCE(FamilyName, '') FROM Person
如果您不使用 COALESCE,如果任何列包含NULL值,您将返回NULL。
不要拿我的话讽刺,我是认真的。除非您正在使用玩具数据库,否则 NULL 是不可避免的,并且在现实世界中我们无法避免 NULL 值。
只是为了说你怎么能有每个人的名字,中间名,姓氏。(中间名和姓氏是可选的,在这种情况下为您提供 NULL)以及如何为博客列表中的每个人提供传真、商务电话、办公电话。
NULLS 很好,检索时必须正确处理它们。在 SQL Server 2008 中,有一个稀疏列的概念,您也可以在其中避免为 NULL 占用空间。
不要将 NULL 与零和任何其他值混淆。人们这样做,任何人都说这是对的。
谢谢纳文
我当天有争议的观点——在数据库列中默认允许 NULL 可能是所有 RDBM 领域中普遍接受的最糟糕的设计决策。每个供应商都这样做,这是错误的。NULL 在某些、特定的、经过深思熟虑的实例中很好,但是您必须为每一列明确禁止 NULL 的想法使得疏忽的可空性方式比它应该的更常见。
我认为问题归结为您将 NULL 值解释为表示什么。是的,NULL 值有很多解释,但其中一些张贴在这里永远不应该使用。NULL 的真正含义取决于您的应用程序的上下文,并且不应仅表示一件事。例如,一个建议是,出生日期字段中的 NULL 表示此人还活着。这是危险的。
简而言之,定义 NULL 并坚持下去。我用它来表示“此时此字段中的值未知”。这意味着,仅此而已。如果您需要它来表示其他含义,那么您需要重新检查您的数据模型。
就个人而言,我认为只有当您将该字段用作另一个表的外键时才应该使用空值,以表示该记录不链接到另一个表中的任何内容。除此之外,我发现在编写应用程序逻辑时,null 值实际上非常麻烦。由于在大多数编程语言中对于许多数据类型没有直接表示数据库空值,因此最终会创建大量应用程序代码来处理这些空值的含义。当数据库遇到空整数时,例如尝试向其添加值 1(也称为 null + 1),数据库将返回 null,因为这就是逻辑的定义方式。但是,当一种编程语言尝试添加 null 和 1 时,通常会抛出异常。因此,您的代码最终会检查当值为空时要做什么,
这一切都归结为规范化与易用性和性能问题。
如果您要坚持完整的规范化规则,您最终将编写如下内容:
选择 c.id, c.lastname,....... from customer c left join customerphonenumber cpn on c.id = cpn.customerid left join customeraddress ca on c.id = ca.customerid left join customerphonenumber2 cpn2 on c. id = cpn2.customerid 等等等等
似乎如果 null 在属性的上下文中是有效的,那么它应该被允许。
但是 null 是什么意思?这就是问题所在。它是“没有价值”,但有十几个不同的原因可能没有价值,并且“null”不会给你任何线索,在这种情况下它意味着哪一个。(尚未设置,不适用于此实例,不适用于此类型,未知,不可知,未找到,错误,程序错误,...)
这在对象引用通常为空的 Java 中很常见。
有一种观点认为空引用也很糟糕。同样的问题:null 是什么意思?
IIRC,Java 同时具有“null”和“未初始化”(尽管后者没有语法)。因此,高斯林意识到对每一种“无价值”都使用“null”是愚蠢的。但为什么只停止两个?
使用 null 绝对没问题。
相关问题:如何在我的数据库中实施数据完整性规则?
我最初从许多具有几乎为零的 nullalbe 字段的小表开始。然后我了解了 LINQ to SQL IsDiscriminator 属性,并且 LINQ to SQL 只支持单表继承。因此,我将它重新设计为一个包含许多 nullalbe 字段的表。
作为一名拥有 30 年经验的分析师/程序员,我只想说 NULL 应该被取出并摆脱他们的痛苦。
-1、01/01/0001/12/31/9999 和 ? 没有处理这些讨厌的 NULL 所需的思维扭曲代码就足够了。