我记得读过一篇有关数据库设计的文章,并且我还记得它说您应该具有NOT NULL的字段属性。我不记得为什么会这样。

我似乎能想到的就是,作为应用程序开发人员,您不必测试NULL和可能不存在的数据值(例如,用于字符串的空字符串)。

但是,对于日期,日期时间和时间,该怎么办(SQL Server 2008)?您必须使用一些历史性的或触底反弹的日期。

对此有任何想法吗?

评论

此答案对NULLdba.stackexchange.com/questions/5176/…的使用情况有深入了解。

真?如果我们不应该使用RDBMS,为什么它完全允许我们使用NULL?只要您知道如何处理NULL,就没有错。

这是BI数据建模吗?通常,您不应该在事实表中允许使用null…否则,正确使用null就是您的朋友。 =)

@ Fr0zenFyr,只是因为RDBMS允许我们做某事,所以这样做不一定是一个好主意。没有什么可以强迫我们在表中声明主键或唯一键,但是除了少数例外,我们还是这样做。

我认为,对此主题的完整处理必须参考Codd的原始要求,即RDBMS必须具有系统性的方法来处理丢失的数据。在现实世界中,有些情况下会创建数据位置,但是没有可放入的数据。无论涉及数据库设计,应用程序编程或两者,Data Architect都必须对此做出一些响应。 SQL NULL不能满足要求,但总比没有好。

#1 楼

我认为这个问题的措辞很差,因为措辞暗示您已经确定NULL是不好的。也许您的意思是“我们应该允许NULL吗?”

无论如何,这是我的看法:我认为NULL是一件好事。当仅因为“ NULL不好”或“ NULL很难”而开始防止NULL时,就开始构成数据。例如,如果您不知道我的出生日期怎么办?在知道之前,您将在列中输入什么?如果您像许多反NULL人士一样,则需要输入1900-01-01。现在,我将被安置在老年病房,可能会接到当地新闻台的电话,祝贺我长寿,询问我过这么长寿的秘密,等等。

如果可以在您可能不知道列值的地方输入行,我认为NULL比选择一些任意的令牌值来表示它是未知的事实有意义得多-其他人将使用该值必须已经知道,进行逆向工程或询问周围的原因。
尽管有一个平衡点-并非数据模型中的每一列都应该为空。表单上通常有可选字段,或者在创建行时不会收集到的某些信息。但这并不意味着您可以推迟填充所有数据。 :-)

在现实生活中,使用NULL的能力可能会受到关键要求的限制。例如,在医学领域,知道为什么值未知是一件生死攸关的事情。心率是否为NULL是因为没有脉搏,还是因为我们还没有测量脉搏?在这种情况下,我们可以将NULL放入心率列中,并在注释或其他列中加上NULL(因为原因)吗?

不要害怕NULL,但愿意学习或指示何时何地应该使用它们,何时何地不应该使用它们。

评论


“一些任意的令牌值表示未知的事实”,这被称为前哨值

–亚历山大
17-10-7在6:23

但是,是什么导致您无法在存储生日的地方创建单独的表birth_date呢?如果出生日期未知,则不要在出生日期中插入出生日期。空是灾难。

– Eldar Agalarov
17年11月16日在23:07

@EldarAgalarov听起来像是特朗普的推理(“灾难”为什么?如何?对于谁?您认为某事是“灾难”,但事实并非如此)。无论如何,出生日期只是一个例子。如果您的人员,成员或客户有15个可能为空的列,您是否要创建15个辅助表?如果您有50个怎么办?如果您的DW事实表有500个怎么办?将严重的可怕的NULL排除在数据库之外的维护工作,是您担心的“灾难”的十倍...

–亚伦·伯特兰(Aaron Bertrand)
17年11月17日,0:43



@AaronBertrand,如果您的表有15个可能为空的列,则闻起来确实很糟糕^^并不是说大量的列本来就不好,但是这可能表示设计错误或需要进行非规范化。但这会引起疑问。

– Programaths
18年1月7日在11:27

@Wildcard因此,您从未见过人们存储1900-01-01来避免使用NULL日期/时间值吗?好吧。此外,NULL =未知,unknown =假。我不确定这可能会导致什么问题,而不是人们不是天生就知道这一点(就像他们不是天生就知道复杂的RDBMS中固有的很多东西一样)。再次挥舞着双手,说:“问题!灾难!”并非如此。

–亚伦·伯特兰(Aaron Bertrand)
18-09-18在13:02



#2 楼

确定的原因有:


NULL不是值,因此没有内部数据类型。当原本依赖实际类型的代码也可能会收到未类型化的NULL时,Null需要到处进行特殊处理。
NULL打破了二值(熟悉的True或False)逻辑,并需要三值逻辑。即使正确实施,这也要复杂得多,并且大多数DBA以及几乎所有非DBA肯定对此了解不多。结果,它肯定会在应用程序中引发许多细微的错误。

与实际值不同,任何特定NULL的语义都留给应用程序。

诸如“不适用”,“未知”和“前哨”很常见,还有其他一些。它们经常在同一数据库中甚至在同一关系中同时使用;

它们对于关系型数据库不是必需的,如“如何处理没有空值的缺失信息”中所述。进一步的规范化是尝试删除NULL表的明显的第一步。

这并不意味着绝对不应允许使用NULL。它确实指出,有许多充分的理由在可行的情况下都禁止使用NULL。

它指出,必须通过更好的模式设计,更好的数据库引擎,甚至更好的数据库语言来尽力而为。可以更经常地避免使用NULL。

Fabian Pascal在“ Nulls Nullified”中响应许多参数。

评论


您链接到“如何在没有空值的情况下处理丢失的信息”就很好地说明了为什么我们不能没有空值:在目前的主要RDBMS上,不可能以合理的方式实施一些建议。

–杰克·道格拉斯(Jack Douglas)
2011-09-24 12:28



杰克:是的,但是“当前的实现无法做到”不是维持现状的理由:-)

–木糖
2011-09-24 12:32

这是在说我们不应该因为飞机不完美而飞行吗?

–亚伦·伯特兰(Aaron Bertrand)
2011-09-27 11:48

不,这是说供应商应该停止为空值使用借口,这些空值在40年前可能是有效的,但在合理的保留期限内已经过期了很长时间。 I / O时间不再是80ms的数量级。单CPU周期不再是微秒的数量级。内存限制不再是几个Meg的数量级。与40年前不同,现在不存在空值而工作所需的硬件速度和容量确实存在,而成本却并不高昂。他是说该继续前进了。

– Erwin Smout
2012年10月5日13:30

“ NULL混淆”链接已死。

– jpmc26
17年2月17日在22:29

#3 楼

我不同意,空值是数据库设计的基本要素。您也提到过,替代方法是增加已知值以表示缺失或未知。问题在于null被广泛误解,导致使用不当。

Idd,Codd建议可以通过设置两个null标记来改善null的当前实现(表示不存在/缺失)而不是“不存在但不适用”和“不存在但不适用”。无法设想此人将如何改善关系设计。

评论


我建议使用一组用户定义的不同类型的null以及与它们一起使用的用户定义的多值逻辑:p

–杰克·道格拉斯(Jack Douglas)
2011-09-24 12:32

这些不是唯一的选择。您排除了标准化的替代方法:代替可能具有或可能没有值的列,而使用可能有或没有第一个表对应行的另一个表。表的含义包含存在或不存在行的含义,并且不存在NULL或哨兵值等的特殊情况。

–木糖
2011-09-24 22:35



NULL的存在不需要特殊的大小写或标记值。这些只是一些人决定如何处理NULL的症状。

–亚伦·伯特兰(Aaron Bertrand)
2011-09-25 20:43

值得注意的是''与PostgreSQL上的null区别(尽管不是Oracle),因此它为null,因此为您提供了双重标记,对于数字列,您可以使用0。但是0的问题是它不适用于外键。

–克里斯·特拉弗斯(Chris Travers)
13年7月7日在6:43

#4 楼

首先,我要说我不是DBA,我是一名开发人员,我会根据需要维护和更新数据库。话虽这么说,但出于几个原因,我也有同样的问题。



空值使开发更加困难且易于出错。
空值使查询存储
空值会占用空间(?固定列长度为?个字节,可变列长度为2个字节)。
空值可以而且经常影响索引和数学运算。




我花了很长时间来筛选整个互联网上的答复,评论,文章和建议。不用说,大多数信息与@AaronBertrand的回复大致相同。这就是为什么我需要回答这个问题的原因。如果您的雇员表具有终止日期字段。终止日期中的空值是因为它是当前未知的将来必填字段。每个在职或终止的员工在某个时候都会在该字段中添加一个日期。我认为这是可空字段的唯一原因。
话虽这么说,同一员工表很可能会保存某种身份验证数据。在企业环境中,通常会在数据库中列出员工的人力资源和会计信息,但并不总是拥有或需要身份验证详细信息。大多数答复会让您相信可以将这些字段为空,或者在某些情况下为它们创建一个帐户,但不要发送凭据。前者将使您的开发团队编写代码以检查NULL并进行相应的处理,而后者则构成了巨大的安全风险!系统中从未使用过的帐户只会增加黑客可能使用的访问点数量,而且它们会为未使用的内容占用宝贵的数据库空间。

鉴于以上信息,最好的是处理将使用的可空数据的方法是允许可空值。这是可悲但真实的事情,您的开发人员会为此而讨厌您。第二种可为空的数据类型应放在相关表中(IE:帐户,凭据等),并具有一对一的关系。除非有必要,否则这允许用户不使用凭据而存在。这消除了额外的安全风险,宝贵的数据库空间,并提供了更整洁的数据库。

下面是一个非常简单的表结构,它显示了必需的可空列和一对一关系。



我知道自几年前提出这个问题以来,我来晚了一点,但希望这将有助于阐明这个问题以及如何最好地解决用它。

评论


我只是对其进行更改,以便在员工记录中没有TerminationDate,但是有一个TerminatedEmployee的表,当员工终止时,该员工将被应用程序移动到该表(而不复制)。显然,这在Account表中很好用,因为TerminatedEmployee表上没有链接的帐户。如果您仍然需要电话号码,我将反转外键,以便employee和终止雇员表具有电话号码的id,而不是相反的方式。

–Programster
17年9月13日在9:01



我真的可以继续几天讨论为什么这会不好。冗余表,不良的SQL做法,使开发人员不得不在两个位置查找员工数据,报告问题,直接给不存在的员工的URI(已被移动)的问题,并且清单继续等等。对于总有一天会有价值的字段,使用NULL完全没问题,这是另一回事,即字段永远不会被填充且永远不会被使用。进行这项工作的许多潜在问题和解决方法不值得在字段上检查NULL的小问题。

–尼古拉斯·阿奎尔(Nicholas Aguirre)
17年9月14日在13:23

我不同意。唯一多余的是,终止日期的空字段可能永远不会填满。开发人员只需在适当的表中查找所需的数据,即可提高性能。如果由于某种原因,您想要终止的雇员和未终止的雇员,都可以通过加入来解决,但是您的应用程序有90%的时间可能想要一个或另一个。我认为我指定的布局更好,因为不可能有雇员的离职日期,而仍然有一个帐户。

–Programster
17年9月14日在17:27

我不是说冗余数据,而是说冗余表。另外,对employee表的任何更改都必须细化到终止的表;这会使应用程序易于出错,并使开发人员的工作更加困难。另外,“终止日期”字段将填写给几乎所有用户。创建第二个相同的表结构并四处移动数据既浪费又成问题。每次都不包含测试以确保表数据已移动并清除。从表中删除数据是不好的做法,即使只是移动它也是如此。如果您如此关注单个字段,那么...

–尼古拉斯·阿奎尔(Nicholas Aguirre)
17年9月18日在14:26

...这几乎总是会被及时填满,然后创建一个与员工保持1to1关系的终止表。作为一名DBA和一名开发人员,我整天都在处理各种数据库,但很高兴我还没有遇到您建议的结构。特别是从开发人员的角度来看,编写和错误检查所有内容将是一场噩梦,因为您不知道它来自哪个表。即使编写联接,返回到软件的数据也会有一个包含空数据的字段,这仍然需要您对其进行测试。

–尼古拉斯·阿奎尔(Nicholas Aguirre)
17年9月18日在14:39

#5 楼

除了使NULL令人困惑的开发人员遇到的所有问题之外,NULL还具有另一个非常严重的缺点:性能

NULL列对于性能而言是一个灾难。以整数算术为例。在没有NULL的理性世界中,使用SIMD指令对数据库引擎代码中的整数算术进行矢量化处理以“每CPU周期快于1行的速度”执行几乎任何计算都是很容易的。但是,在引入NULL的那一刻,您需要处理NULL创建的所有特殊情况。现代CPU指令集(阅读:x86 / x64 / ARM和GPU逻辑)根本无法有效地做到这一点。

以划分为例。在非常高的层次上,这是使用非null整数所需的逻辑:

if (b == 0)
  do something when dividing by error
else
  return a / b


使用NULL,这变得有些棘手。如果b为空,则与b一起需要一个指示器,并且对于a同样需要一个指示器。现在检查变为:

if (b_null_bit == NULL)
   return NULL
else if (b == 0) 
   do something when dividing by error
else if (a_null_bit == NULL)
   return NULL
else 
   return a / b


在现代CPU上运行NULL运算要比不使用null运算要慢得多(大约2-3倍)。

介绍SIMD时,情况会变得更糟。使用SIMD,现代的Intel CPU可以在一条指令中执行4 x 32位整数除法,如下所示:

x_vector = a_vector / b_vector
if (fetestexception(FE_DIVBYZERO))
   do something when dividing by zero
return x_vector;

SIMD也会登陆,但这需要使用更多的向量和CPU寄存器并进行一些巧妙的位屏蔽。即使有一些好的技巧,即使是相对简单的表达式,NULL整数算术的性能损失也会缓慢降低5-10倍。

上面的类似内容在聚合和某种程度上对于连接同样适用。 。

换句话说:SQL中NULL的存在是数据库理论与现代计算机的实际设计之间的阻抗不匹配。 NULL有一个很好的理由使开发人员感到困惑-因为整数在大多数理智的编程语言中都不能为NULL-但这不是计算机的工作方式。

#6 楼

有趣的问题。


我所能想到的就是,作为应用程序开发人员,您不必测试NULL和可能不存在的数据值(例如,空字符串)。


比这更复杂。 Null具有许多不同的含义,并且在许多列中不允许使用null的一个非常重要的原因是,当该列为null时,则意味着只有一件事(即,它没有出现在外部联接中)。此外,它还允许您设置数据输入的最低标准,这确实很有用。


但是,如果使用日期,日期时间和时间,该怎么办(SQL Server 2008)?您必须使用一些历史性的或触底的日期。


这说明了立即存在null的问题,即存储在表中的值可能意味着“该值确实不适用”或“我们不知道”。对于字符串,空字符串可以用作“这不适用”,但是对于日期和时间,则不存在此类约定,因为没有常规意义上的有效值。通常,您会因使用NULL而陷入困境。

有一些方法可以解决此问题(通过添加更多关系和联接),但这些方法与在数据库中包含NULL的语义清晰性问题完全相同。对于这些数据库,我不会为此担心。实际上,您无能为力。

编辑:外键是NULL不可缺少的一个区域。在这里,它们通常只有一个含义,与外部连接含义中的null相同。当然,这是一个例外。

#7 楼

Wikipedia上有关SQL Null的文章对NULL值进行了一些有趣的评论,并且作为与数据库无关的答案,只要您知道特定RDBMS具有NULL值的潜在影响,它们在您的设计中就可以接受。如果不是,则无法将列指定为可为空。

请注意,RDBMS如何在SELECT操作(例如数学)和索引中处理它们。

#8 楼

哇,正确的答案“在不必要时不要允许NULL,因为它们会降低性能”是不知何故的最后答案。我会投票赞成并加以阐述。当RDBMS允许非稀疏列为NULL时,该列将添加到位图,该位图跟踪每个单独行的值是否为NULL。因此,通过向表中的所有列均不允许为NULL的列添加NULL功能,可以增加保存表所需的存储空间。此外,您还要求RDBMS读取和写入位图,从而降低所有操作的性能。

在许多情况下,允许NULL将破坏3NF。尽管我不像我的许多同事那样对3NF坚持不懈,但请考虑以下情形:在Person表中有一个列,称为DateOfDeath,该列可以为空。如果一个人死亡,将使用其DateOfDeath填充,否则将保留为NULL。还有一个称为IsAlive的不可为空的位列。如果此人还活着,则此列设置为1;如果该人已死,则此列设置为0。绝大多数存储过程都使用IsAlive列,它们仅在一个人还活着的情况下关心它们,而不关心它们的DateOfDeath。但是,IsAlive列破坏了数据库规范化,因为它完全可以从DateOfDeath派生而来。但是,由于IsAlive硬连接到大多数SP,因此直接的解决方案是使DateOfDeath不可为空,并在此人还活着的情况下为该列分配默认值。然后,可以重写几个使用DateOfDeath的SP,以检查IsAlive列,并且仅在此人还活着时才尊重DateOfDeath。同样,由于大多数SP只关心IsAlive(有点),而不关心DateOfDeath(日期),因此使用此模式可大大加快访问速度。

有用的T-SQL脚本,用于查找没有所有模式中的NULL为:

select 'IF NOT EXISTS (SELECT 1 FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' WHERE ' + QUOTENAME(c.name) + ' IS NULL)
    AND (SELECT COUNT(*) FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ') > 1 PRINT ''' + s.name + '.' + t.name + '.' + REPLACE(c.name, '''', '''''') + ''''
    from sys.columns c
    inner join sys.tables t ON c.object_id = t.object_id
    inner join sys.schemas s ON s.schema_id = t.schema_id
    where c.is_nullable = 1 AND c.is_computed = 0
    order by s.name, t.name, c.name;


如果在生产数据库的副本上运行此命令,则可以找到开发人员标记为允许NULL的列,而这些列实际上没有NULL。其中的绝大多数可以标记为NOT NULL,从而提高性能并降低存储空间。

不可能消除所有表中的所有NULL并仍然具有简洁的设计,但是在消除尽可能多的NULL方面具有相当大的优势。优化器利用这些信息可以更快地工作,并且如果您可以消除表中的所有NULL,则可以重新获得大量的存储空间。

我知道性能并不是DBA会考虑的全部这么多,但是您只能在解决方案中投入有限的内存和处理器功能,这时您将不得不开始考虑逻辑和物理设计。

还要注意仅适用于真正的RDBMS,我将答案的技术部分基于SQL Server。列出的T-SQL也可以从SQL Server中查找没有空值的可空列。

评论


评论不作进一步讨论;此对话已移至聊天。

–保罗·怀特♦
17-2-25在14:06