如何避免数据库中的NULL,同时还表示缺少数据?

时间:2010-12-02 15:36:11

标签: sql database database-design schema data-modeling

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为Paris。在这种情况下,伦敦&lt;&gt;巴黎,所以查询是真的。现在假设p.city不是Paris,并且实际上是 xyz 。在这种情况下,(伦敦&lt;&gt; xyz )或( xyz &lt;&gt; Paris)也是True。所以,给定任何数据 - 这个查询都是真的。但是,如果xyz为“NULL”,则场景会发生变化。在这种情况下,这两个表达式都不是True 也不是 False,它们实际上是Unknown。在这种情况下,因为结果未知,您将获取任何返回的行。

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

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

8 个答案:

答案 0 :(得分:43)

每个人都在说话,除了dportas和Walter之外,没有人能够理解这个问题。好吧,所以有95%的SO人不理解Null问题,并且因为他们的数据库充满了Null而感到受到威胁,他们想要转换寻求者。无价。当他们争论时,有人会如何学习?

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

当然,如果禁止空值,则必须通过其他方式处理缺失的信息。不幸的是,这些其他方法太复杂了,不能在这里详细讨论。

实际上它并不是那么难。有三种选择。

  1. 这是H Darwen撰写的一篇关于How To Handle Missing Information Without Using NULL的论文,可能有助于解决问题。
  2. 1.1。第六范式就是答案。但您无需将整个数据库规范化为6NF。对于每个可选的列,您需要一个远离主表的子表,只需要PK,它也是FK,因为它是1 :: 0-1关系。除PK外,唯一的列是可选列。

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

      

    (对于OO人来说,顺便说一下,这是一个关系表示法的三级类图,一个“Concwete Table Inheritance”,没什么大不了的,我们已经有30年了。)

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

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

    如果你不想走那么远(6NF),你还有两个选择 。
    2.您可以使用Null替代品。我使用CHAR(0)表示字符colomns,0表示数字。但我不允许FK这样做。显然,您需要一个超出正常数据范围的值。这不允许三值逻辑 。
    3.除了(2)之外,对于每个Nullable列,您需要一个布尔指示符。对于Sex列的示例,指标类似于SexIsMissingSexLess(抱歉)。这允许非常紧凑的三值逻辑。 5%的人中有很多人喜欢它,因为数据库仍然是5NF(并且表格较少);缺少信息的列加载了从未使用过的值;它们仅在指标为假时使用。如果您有一个企业数据库,则可以将其包装在一个函数中,并始终使用UDF,而不是原始列。

    当然,在所有情况下,您都永远无法编写处理缺失信息所需的代码。无论是ISNULL(),还是6NF列的子查询,还是在使用该值或UDF之前要检查的指标。

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

答案 1 :(得分:19)

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

实际上很容易。你设计的是,每当你不得不丢失一些数据时,你可以通过丢失整行来实现。如果没有行,则不是一行满NULL。它显然不存在。

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

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

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

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

答案 2 :(得分:6)

非常简单地仅存储已知信息 - 换言之,即封闭世界假设。目标是至少参加博伊斯·科德/第五范式,你不会错误。

答案 3 :(得分:4)

null是理论符合现实的结果,必须进行调整才能使用。在我看来,试图避免所有null值最终会导致更糟糕和更难维护的代码,而不仅仅是在适当情况下使用null

答案 4 :(得分:3)

需要NULL - 不需要替换它们

NULL的enitre定义是它的未知 - 只需用任意类型替换它就是做同样的事情,为什么呢?

以下评论:

试过这个 - 两者都没有:

declare @x char
set @x = null

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

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

我只能认为这意味着因为null是未知的,所以不能说它等于或不等于 - 因此两个语句都是假的

答案 5 :(得分:1)

可以/应该使用

NULL

A )您有业务原因。例如,在付款表格中,NULL付款值意味着从未付款。 0.00付款金额意味着我们故意不付款。对于医疗图表,血压读数的NULL值意味着您没有服用血压,0值意味着患者已经死亡。这是重要区别,在某些应用程序中是必需的。

B )您的查询会对此进行说明。如果您了解NULLINEXISTS,不等运算符(如您在OP中指定的那样)等的影响,那么它应该不是问题。如果您的表中现在有NULL并且不想要某些应用程序的值,则可以使用视图COALESCEISNULL来填充不同的值,如果源表具有NULL

修改

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

你和另外3个人一起参加派对。你知道有一个人被命名为“约翰”但却不知道其他人。

逻辑,“有多少人被命名为Joe”的答案未知或NULL。在SQL中,这将类似于

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

你的不平等将是:

SELECT name from party where NAME <> 'Joe'你只能得到“约翰”的回报值,因为约翰的名字就是你所知道的。其他人可能是也可能不是乔,但你无从知晓。

答案 6 :(得分:0)

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

针对您有关性别的具体问题:

  • 您确定需要性别表并为每个查询产生额外加入的费用吗?对于简单的枚举类型,将字段设为int并定义1 =男性,2 =女性,NULL =未知是不合理的。

答案 7 :(得分:0)

如果可能的话,不允许将列定义为NULL。对我而言,它与你想要的业务规则没有任何关系,这意味着它与磁盘I \ O有关。

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

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