如何在我的数据库中避免 NULL,同时还表示丢失的数据?

Posted

技术标签:

【中文标题】如何在我的数据库中避免 NULL,同时还表示丢失的数据?【英文标题】:How can I avoid NULLs in my database, while also representing missing data? 【发布时间】:2011-05-19 05:05:12 【问题描述】:

在SQL and Relational Theory(C.J. Date, 2009)第 4 章中提倡避免重复行,同时也避免在我们存储的数据中使用NULL 属性。虽然避免重复行没有问题,但我正在努力了解如何在不使用NULL 的情况下对数据进行建模。以以下为例 - 这有点工作。

我们有一个artist 表,其中除其他列外,还有一个gender 列。这是gender 表的外键。然而,对于一些艺术家,我们不知道他们的性别——例如,我们得到了一份新音乐列表,其中没有艺术家的描述。如果不使用NULL,如何代表这些数据?我看到的唯一解决方案是在gender 表中添加一个新的性别“未知”。

虽然我非常喜欢这本书,但当本章结束时我真的很失望:

当然,如果禁止使用空值,则必须通过其他方式处理丢失的信息。不幸的是,这些其他方法过于复杂,无法在此详细讨论。

这真是令人遗憾 - 因为这是我等待阅读的解决方案!有一个参考阅读附录,其中有很多出版物可供阅读,但在我深入阅读这些之前,我希望能得到更多的实际总结。


我得到一些人的评论,他们不明白为什么我希望避免“NULL”,所以我将再次引用这本书。进行以下查询:

SELECT s.sno, p.pno
  FROM s, p
 WHERE s.city <> p.city
    OR p.city <> 'Paris'

现在,以 s.city 为伦敦,p.city 为巴黎为例。在本例中,伦敦 巴黎,所以查询为真。现在假设 p.city 不是巴黎,实际上是 xyz。在这种情况下,(London xyz) OR (xyz Paris) 也是 True。所以,给定任何数据——这个查询是真的。但是,如果 xyz 为“NULL”,则情况会发生变化。在这种情况下,这两个表达式都不是 True 也不是 False,它们实际上是 Unknown。在这种情况下,由于结果未知,您将不会返回任何行。

从 2 值逻辑到 3 值逻辑的转变很容易引入这样的错误。事实上,我刚刚在工作中介绍了一个激发这篇文章的动机。我想要type != 0 所在的所有行但是,这实际上最终匹配type == 0 OR type IS NULL - 令人困惑的行为。

未来我是否使用NULL 对我的数据进行建模尚不清楚,但我很好奇其他解决方案是什么。 (我也一直认为如果你不知道,你应该使用NULL)。

【问题讨论】:

NULL 和 unknown 是不同的。 NULL 表示不填写;未知表示您目前无法确定。 “理论上,理论和实践之间没有区别。在实践中,有” - Jeff Atwood 空值总是会导致不正确的结果 - 即与它们应该代表的任何事物的现实和逻辑不匹配的结果。由于没有它们,所有信息都可以准确表示,因此仅在特殊情况下才应使用空值。 性别表有多少行? 男/女/文件未找到 【参考方案1】:

你很好,消除了 Null。我从来没有在我的任何数据库中允许 Null。

当然,如果禁止使用空值,那么丢失的信息将不得不通过其他方式处理。不幸的是,这些其他方法过于复杂,无法在此详细讨论。

其实一点也不难。有三种选择。

    这是 H Darwen 撰写的关于 How To Handle Missing Information Without Using NULL 的论文,它可能有助于解决这个问题。

    1.1。第六范式就是答案。但是您不必将您的整个数据库标准化为 6NF。对于可选的每一列,您需要一个主表之外的子表,只有 PK,这也是 FK,因为它是 1::0-1 关系。除了 PK,唯一的列是可选列。

    看看这个Data Model;第4页的AssetSerial是一个经典案例:不是所有Assets都有SerialNumbers;但是当他们这样做时,我希望他们存储它们;更重要的是,我想确保它们是唯一的。

    (顺便提一下,对于那些面向 OO 的人来说,这是一个关系表示法的三层类图,一个“具体表继承”,没什么大不了的,我们已经有 30 年了。)

    1.2。对于每个这样的表,使用视图来提供表的 5NF 形式。当然,使用 Null(或适用于该列的任何值)来识别任何行的列不存在。但不要通过视图更新。

    1.3 不要使用直连接来抓取 6NF 列。也不要使用外连接(并让服务器为缺失的行填充 Null)。使用子查询来填充列,并指定要为缺失值返回的值(除非您有 Oracle,因为它的子查询处理甚至比它的集合处理更差)。例如。只是一个例子。您可以将数字列转换为字符串,并对缺失的行使用“Missing”。

如果您不想走那么远 (6NF),您还有两个选择。

    您可以使用 Null 替换。我将 CHAR(0) 用于字符列,将 0 用于数字。但我不允许 FK 这样做。显然,您需要一个超出正常数据范围的值。这不允许三值逻辑。

    除了 (2) 之外,对于每个 Nullable 列,您还需要一个布尔指示符。对于Sex 列的示例,指标类似于SexIsMissingSexLess(抱歉)。这允许非常严格的三值逻辑。这 5% 中的许多人都喜欢它,因为 db 保持在 5NF(以及更少的表);缺少信息的列加载了从未使用过的值;它们仅在指标为假时使用。如果您有企业数据库,则可以将其包装在函数中,并始终使用 UDF,而不是原始列。

当然,在所有情况下,您都无法避免编写处理丢失信息所需的代码。无论是ISNULL(),还是 6NF 列的子查询,还是使用值前要检查的 Indicator,还是 UDF。

如果 Null 有特定含义... 那么它就不是 Null!根据定义,Null 是未知值。

【讨论】:

+1 以获得 H Darwen 论文的链接。然而,在我看来,他从一个表的案例研究开始,在这个讨论中,没有人会首先允许 NULL。如果有一个所有意见都存在分歧的例子就好了,比如说 50-50。 感谢您的回答!是的,可悲的是,很多人似乎并没有真正理解我的观点,只是假设我没有“得到”NULL——这根本不是真的;我只是不认为任何事情是最终的方法,并且喜欢保持我的选择开放。再次感谢! 这种“解决方案”总是让我觉得被骗的地方是:“指定你想要为缺失值返回的值”。如果你打算有一个“哨兵值”,那么你要么必须在某种类型中保留这样的值(例如0''),要么你需要将你的类型与不属于领域。这就是null 首先的用途。所以你似乎回到了你开始的地方。 @IMSoP。呃没有。我没有null,所以我没有问题。你有空,和问题。你习惯了兜圈子,一遍遍地看起点,所以你认为别人也是这样。我不。无论如何,我不是在回答你的问题。不要用我的解决方案,用你的“解决方案”,不要担心另一半的生活。如果你猜测它们,你肯定会崩溃,并欺骗自己。如果您真的不明白,请提出问题,而不是发表声明。 @PerformanceDBA 我不确定你为什么要亲自处理这个问题。如果我说的话以任何方式冒犯了你,我很抱歉。我并不是说我“感觉被 欺骗了”,而是声称“NULL 是不必要的”,而实际上它似乎归结为“NULL 可以更好地实现”。我的问题是:当您引入哨兵值时(例如在第 1.3 点或第 2 点中),在某些情况下是否不需要重新实现 3VL,例如回答“工资 > 5000 美元?”的问题。对于带有“salary = 'Unknown'”的项目?如果是这样,这与谨慎使用 NULL 有何不同?【参考方案2】:

那么你如何设计没有NULLS?那是最初的问题。

其实很简单。您的设计使得每当您必须丢失一些数据时,您可以通过丢失整行来做到这一点。如果一行不存在,则它不是充满 NULL 的行。它根本不存在。

因此,在“DateOfDeath”的情况下,我们有一个包含两列的表,即 PersonId 和 DateOfDeath。 PersonId 引用 Persons 表中的 Id。如果没有要存储的 DateOfDeath,我们不存储该行。讨论完毕。

如果您在此表和 Persons 表之间进行 OUTER JOIN,那么在没有行的地方,您将得到 DateOfDeath 的 NULL。如果你在 where 子句中使用它,你会得到关于 3 值逻辑的通常令人困惑的行为。如果您执行 INNER JOIN,则没有 DateOfDeath 的行将从连接中消失。

允许每列强制执行 NOT NULL 的设计被称为第六范式。

说了这么多,我经常允许在非关键列中使用 NULL。而且我没有简洁的方式告诉你我如何确定一个列是关键的。

【讨论】:

简洁、非宗教、实用的建议结束。很好的答案! 6NF 与空值无关。【参考方案3】:

很简单,只存储已知信息——换句话说,封闭世界假设。目标是至少处于 Boyce Codd / 第五范式,这样你就不会出错。

【讨论】:

你有什么关于封闭世界假设的推荐读物吗?我似乎也需要去阅读 3NF 之后的额外阅读。谢谢!【参考方案4】:

nulls 是理论与现实相遇的结果,必须进行调整才能使用。在我看来,试图避免所有 null 值最终会导致比仅在适当的情况下使用 null 更难看且更难维护的代码。

【讨论】:

我同意。理论是实用的,但这个真理有其局限性。从理论上讲,理论与实践没有区别。在实践中,有。问题通常来自于数据库文档中没有明确说明,给定的 NULL 实际意味着什么,除了“这里没有数据”。 null 是一个有效值。数据库对此没有任何意义。应用程序文档应记录超出“无数据”的含义。【参考方案5】:

NULL 是必需的 - 无需替换它们

NULL 的全部定义是它的未知 - 简单地用任意类型替换它就是做同样的事情,那为什么呢?

对于下面的cmets:

刚刚试过 - 两者都不是真的:

declare @x char
set @x = null

if @x = @x
begin
select 'true'
end

if @x <> @x
begin
select 'false'
end

我只能认为这是因为 null 是未知的,所以不能说它等于或不等于 - 因此两个陈述都是错误的

【讨论】:

同意。无法理解为什么 OP 想跳过 NULL 空值表示未知的误解很常见,但显然是不正确的(至少在 SQL 中)。 SQL 不会那样使用空值。想想表达式 x=x。如果 x 未知,则该表达式的结果将为 TRUE。但是,如果 x 为 null,则情况并非如此。因此 null 并不意味着“未知”。 @ m.edmondson :您的示例说明了问题,但您的结论肯定是错误的。在数学、逻辑和现实世界中,如果 x 未知,则 x = x 为 TRUE。如果不是,那么代数和大多数科学都是不可能的。 Null 与“未知”的东西不同,如果你假装它是,那么你肯定会从你的 SQL 中得到错误的结果——比如你在这里演示的那个。 aCiD2 - 这根本不是真的。如果我有两种水果但没有告诉你是哪一种,你就不能确定它们是相同的还是不同的,因为你有一个未知的 x=x 为真的断言背后有两个假设。第一个假设是“x”的两个实例指的是同一个东西。第二个假设是,虽然 x 的值未知,但仍然可以断言 x 具有值。这些都是合理的假设,但如果要使用 NULL 来传达消息,则值得以某种方式明确说明它们。我更喜欢将 NULL 视为消息的非通信。这与某种默认消息的通信不同。【参考方案6】:

NULL 可以/应该使用,只要:

A) 您有商业原因。例如,在付款表中,NULL 付款值表示从未付款。 0.00 支付价值意味着我们故意不支付任何费用。对于医疗图表,血压读数的NULL 值表示您没有测量血压,0 值表示患者已经死亡。这是一个显着的区别,在某些应用中是必需的。

B) 您的查询说明了这一点。如果您了解NULLINEXISTS、不等式运算符(如您在OP 中指定)等的影响,那么这应该不是问题。如果您的表中现在有 NULL 并且不希望某些应用程序的值,您可以使用视图和 COALESCEISNULL 如果源表有 NULL 来填充不同的值。

编辑:

使用NULL 解决 OP 关于“现实世界”不等式/等式的问题,这是我有时使用的一个很好的例子。

您正在和另外 3 个人参加聚会。您知道一个人叫“John”,但不认识其他人。

逻辑上,“有多少人叫乔”的答案是未知的或NULL。在 SQL 中,这类似于

SELECT name FROM party where NAME = 'Joe' 你不会得到任何行,因为你不知道他们的名字。他们可能是也可能不是乔。

你的不等式是:

SELECT name from party where NAME &lt;&gt; 'Joe' 你只会得到“John”的返回值,因为你只知道 John 的名字。其他人可能是也可能不是乔,但你无法知道。

【讨论】:

@dportas :如果您知道没有付款,使用NULL 比添加“未付款”列容易得多。 给 NULLS 赋予意义就像给狗在夜间吠叫失败赋予意义一样。它可能非常聪明,但也可能非常冒险。例如,死亡日期中的 NULL 可能表示此人还活着,但也可能表示在死亡日期留空的情况下输入了死亡证明。 @Valentin -- 不说付款与说没有付款是两回事。 @JNK。哇。您将大量字符信息打包到数字列中。或者你所有的列都是VARCHARS。您谈论业务的方式是关于人,而不是关于适当的业务决策,基于已知事实。 @JNK:当然。阅读我的答案,以及人们留下的 cmets。【参考方案7】:

我不同意作者的观点,并声称 NULL 实际上是处理可选字段缺失数据的正确方法。事实上,这就是NULL存在的原因......

对于您关于性别的具体问题:

您确定需要一个性别表并为每个查询产生额外的连接成本吗?对于简单的枚举类型,将字段设为 int 并定义 1=male, 2=female, NULL=unknown 并非不合理。

【讨论】:

你不同意 Codd & Date,我喜欢它!!!您已经编写了关系模型的继任者,是吗? ENUM 不是 ISO/IEC/ANSI 标准 SQL;它是城镇免费软件的非标准扩展。连接没什么好害怕的。【参考方案8】:

尽可能不要将列定义为 NULL。对我来说,它与您希望 NULL 表示它与磁盘 I\O 的业务规则无关。

在 SQL Server 中,一个可为空的列,比如一个字符 10,当为空时将占用位图中的一位,而当不可为空时将占用 10 个字节。那么空值如何损害磁盘 I/O。它伤害的方式是当一个值被插入到曾经是空值的列中时。由于 SQL 没有保留空间,因此行中没有空间来放置值,因此 SQL Server 必须移动数据以腾出空间。如果这是一个 HEAP,页面拆分、碎片、更新 RID 等都会损害磁盘 I/O。

顺便说一句,如果有性别表,我们可以为“无法确定个人的真实性起源或状态”添加另一行。

【讨论】:

假设你有一台“完美的电脑”——无限的内存(内存+磁盘),速度超快。你还会这样设计吗?如果是,那就这样吧。但是,如果不是,那么您是围绕硬件限制进行设计,而不是围绕业务需求进行设计。在过去约 15 年中,普通 PC 的磁盘空间增加了 1000 多倍,内存至少增加了 250 倍。 @Damir Sudarevic :即使您拥有一台“完美的计算机”,您当然也应该设计没有空值的数据库。原因是为了保证正确性,而不是为了优化硬件。空值会导致不正确的结果,并且不能准确地模拟现实,因此通常应该避免它们。

以上是关于如何在我的数据库中避免 NULL,同时还表示丢失的数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免丢失 Post 请求数据

如何避免循环内的数据丢失

Swift 中如何避免精度丢失

Mongoose:我如何避免回调地狱,同时允许对不返回承诺的 mongoose 方法进行存根?

js 如何同时判断 某个变量不是 undefined 也不是 null也不是 空啊

如何避免 NavigationBar 中的两个 barButtons 可以同时按下?