11

前段时间,我一直在阅读CJ Date的《 SQL 和关系理论》一书。作者以批评 SQL 的三值逻辑(3VL)而闻名。1)

作者对为什么在 SQL 中应避免使用 3VL 提出了一些要点,但他没有概述如果不允许可空列,数据库模型会是什么样子。我对此进行了一番思考,并提出了以下解决方案。如果我错过了其他设计选项,我想听听它们!

1) Date 对 SQL 3VL 的批评反过来也受到了批评:请参阅Claude Rubinson 的这篇论文(包括 CJ Date 的原始批评)。


示例表:

例如,以下表为例,我们有一个可为空的列 ( DateOfBirth):

#  +-------------------------------------------+
#  |                   People                  |
#  +------------+--------------+---------------+
#  |  PersonID  |  Name        |  DateOfBirth  |
#  +============+--------------+---------------+
#  |  1         |  Banana Man  |  NULL         |
#  +------------+--------------+---------------+

NULL选项 1:通过标志和默认值进行模拟:

不是使列可以为空,而是指定任何默认值(例如1900-01-01)。附加BOOLEAN列将指定是否DateOfBirth应该简单地忽略 in 的值,或者它是否实际包含数据。

#  +------------------------------------------------------------------+
#  |                              People'                             |
#  +------------+--------------+----------------------+---------------+
#  |  PersonID  |  Name        |  IsDateOfBirthKnown  |  DateOfBirth  |
#  +============+--------------+----------------------+---------------+
#  |  1         |  Banana Man  |  FALSE               |  1900-01-01   |
#  +------------+--------------+----------------------+---------------+

选项 2:将可为空的列转换为单独的表:

可空列被新表 ( DatesOfBirth) 替换。如果一条记录没有该列的数据,则新表中不会有记录:

#  +---------------------------+ 1    0..1 +----------------------------+
#  |         People'           | <-------> |         DatesOfBirth       |
#  +------------+--------------+           +------------+---------------+
#  |  PersonID  |  Name        |           |  PersonID  |  DateOfBirth  |
#  +============+--------------+           +============+---------------+
#  |  1         |  Banana Man  |
#  +------------+--------------+

虽然这似乎是更好的解决方案,但这可能会导致需要为单个查询连接许多表。由于OUTER JOIN不允许 s (因为它们会引入NULL结果集中),所有必要的数据可能不再像以前那样仅通过单个查询来获取。


问题: 是否还有其他消除方法NULL(如果有,它们是什么)?

4

7 回答 7

5

我看到 Date 的同事 Hugh Darwen 在“如何在不使用 NULL 的情况下处理缺失信息”的精彩演讲中讨论了这个问题,该演讲可在Third Manifesto 网站上找到

他的解决方案是您第二种方法的变体。它是第六范式,表格中包含出生日期和未知的标识符:

#  +-----------------------------+ 1    0..1 +----------------------------+
#  |         People'             | <-------> |         DatesOfBirth       |
#  +------------+----------------+           +------------+---------------+
#  |  PersonID  |  Name          |           |  PersonID  |  DateOfBirth  |
#  +============+----------------+           +============+---------------+
#  |  1         |  Banana Man    |           ! 2          | 20-MAY-1991   |
#  |  2         |  Satsuma Girl  |           +------------+---------------+
#  +------------+----------------+
#                                  1    0..1 +------------+
#                                  <-------> | DobUnknown |
#                                            +------------+
#                                            |  PersonID  |
#                                            +============+
#                                            | 1          |
#                                            +------------+

然后,从 People 中选择需要连接所有三个表,包括用于指示未知 Dates Of Birth 的样板。

当然,这在一定程度上是理论上的。这些天 SQL 的状态仍然不够先进,无法处理所有这些。Hugh 的演讲涵盖了这些缺点。他提到的一件事并不完全正确:某些 SQL 风格确实支持多重赋值 - 例如Oracle 的 INSERT ALL 语法

于 2010-06-20T19:47:43.093 回答
3

我建议你选择你的选项 2。我相当肯定 Chris Date 也会因为本质上你正在做的是完全规范化到6NF ,这是 Date 共同负责引入的最高可能的范式。我支持推荐的 Darwen关于处理缺失信息的论文。

由于不允许使用 OUTER JOIN(因为它们会将 NULL 引入结果集中),因此可能不再像以前那样仅通过单个查询来获取所有必要的数据。

…事实并非如此,但我同意 Darwen 论文中没有明确提到外连接的问题;这是让我想要的一件事。明确的答案可以在 Date 的另一本书中找到……</p>

首先,请注意 Date 和 Darwen 自己的真正关系语言Tutorial D只有一种连接类型是自然连接。理由是实际上只需要一种连接类型。

我提到的 Date book 是优秀的SQL and Relational Theory: How to Write Accurate SQL Code

4.6:关于外连接的评论:“从关系上讲,[外连接是]一种霰弹枪婚姻:它迫使表成为一种联合——是的,我的意思是联合,而不是联合——即使有问题的表不能符合联合的通常要求...实际上,它是通过在进行联合之前用空值填充一个或两个表来做到这一点的,从而使它们毕竟符合那些通常的要求。但是没有理由为什么填充不应该用正确的值而不是空值来完成

使用您的示例和默认值“1900-01-01”作为“填充”,外部连接的替代方案可能如下所示:

SELECT p.PersonID, p.Name, b.DateOfBirth
  FROM Person AS p
       INNER JOIN BirthDate AS b
          ON p.PersonID = b.PersonID
UNION
SELECT p.PersonID, p.Name, '1900-01-01' AS DateOfBirth
  FROM Person AS p
 WHERE NOT EXISTS (
                   SELECT * 
                     FROM BirthDate AS b
                    WHERE p.PersonID = b.PersonID
                  );

Darwen 的论文散文了两个显式表,比如BirthDateBirthDateKnown,但是 SQL 不会有太大的不同,例如用半连接BirthDateKnown代替BirthDate上面的半差异。

请注意上述用途JOININNER JOIN只是因为标准 SQL-92NATURAL JOINUNION CORRESPONDING没有在现实生活中的 SQL 产品中广泛实施(找不到引用,但 IIRC Darwen 主要负责将后两者纳入标准)。

进一步注意,上面的语法看起来很冗长,只是因为 SQL 通常是冗长的。在纯关系代数中,它更像是(伪代码):

Person JOIN BirthDate UNION Person NOT MATCHING BirthDate ADD '1900-01-01' AS DateOfBirth;
于 2011-09-07T11:28:22.700 回答
1

我还没读过,但在第三宣言网站上有一篇名为How To Handle Missing Information Using S-by-C的文章,由 Hugh Darwen 和 CJ Date 运营。这不是 CJ Date 写的,但我认为既然它是该网站上的一篇文章,它可能与他的观点相似。

于 2010-06-20T16:12:58.337 回答
0

一种替代方法可能是实体-属性-价值模型:

 entity  attribute    value
 1       name         Banana Man
 1       birthdate    1968-06-20

如果出生日期未知,则只需省略其行。

于 2010-06-20T16:20:52.657 回答
0

选项 3:记录作者的责任:

CREATE TABLE Person
(
  PersonId int PRIMARY KEY IDENTITY(1,1),
  Name nvarchar(100) NOT NULL,
  DateOfBirth datetime NOT NULL
)

当您的目标是消除它们时,为什么要扭曲模型以允许空表示?

于 2010-06-22T19:57:45.523 回答
0

您也可以使用 消除null输出COALESCE

SELECT personid  /*primary key, will never be null here*/
       , COALESCE(name, 'no name') as name
       , COALESCE(birthdate,'no date') as birthdate
FROM people

并非所有数据库都支持 COALESCE,但几乎所有数据库都有一个称为后备选项
IFNULL(arg1, arg2)或类似的东西会做同样的事情(但仅适用于 2 个参数)

于 2011-09-06T21:43:50.367 回答
0

一种选择是使用显式选项类型,类似于 Haskell 的Maybe仿函数。

不幸的是,许多现有的 SQL 实现对用户定义的代数数据类型的支持很差,甚至对用户定义的类型构造函数的支持也很差,而您确实需要干净地执行此操作。

这只会为您明确要求的那些属性恢复一种“null”,但没有null's 愚蠢的三值逻辑。Nothing == NothingTrue,不是unknownnull

当缺少信息的原因有几个时,对用户定义的代数类型的支持也会有所帮助,例如,与以下 Haskell 类型等效的数据库对于明显的应用程序来说是一个很好的解决方案:

data EmploymentStatus = Employed EmployerID | Unemployed | Unknown

(当然,支持这一点的数据库还需要支持比通常更复杂的外键约束。)

除此之外,我同意APConedaywhen关于 6NF 的回答。

于 2013-05-30T22:39:16.967 回答