有时候,我一直在阅读这本书 SQL和关系理论 到CJ日期. 。作者以批评SQL的三价逻辑(3VL)而闻名。1)

作者对为什么应在SQL中避免3VL提出一些有力的观点,但是他没有概述 如果不允许使用无效的列,数据库模型的样子将如何. 。我已经考虑了一下,并提出了以下解决方案。如果我错过了其他设计选择,我想听听它们!

1) 日期对SQL 3VL的批评又受到批评:请参阅 克劳德·鲁宾逊(Claude Rubinson)的本文 (包括CJ日期的原始批评)。


示例表:

例如,以下表,我们有一个无效的列(DateOfBirth):

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

选项1:仿真 NULL 通过标志和默认值:

指定任何默认值,而不是使列无效(例如 1900-01-01)。额外的 BOOLEAN 列将指定是否值 DateOfBirth 应该简单地忽略或实际包含数据。

#  +------------------------------------------------------------------+
#  |                              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 (如果是这样,他们是什么)?

有帮助吗?

解决方案

我看到日期的同事休·达尔文(Hugh Darwen 第三宣言网站.

他的解决方案是您第二种方法的一种变体。这是第六个正常形式,表格既可以保留出生日期和未知的标识符:

#  +-----------------------------+ 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          |
#                                            +------------+

然后,从人那里选择需要加入所有三张桌子,包括样板,以指示未知的出生日期。

当然,这有点理论。如今,SQL的状态仍然不足以处理所有这些。休的演讲涵盖了这些缺点。他提到的一件事不是完全正确的:SQL的某些口味确实支持多个作业 - 例如 Oracle的插入所有语法.

其他提示

我建议您选择您的选项2。我很确定克里斯的约会也会因为您所做的工作完全正常化 6nf, ,最高的正常形式 日期是共同负责介绍的。我第二次推荐 达尔文的论文 处理丢失的信息。

由于不允许外部连接(因为它们会将null引入结果集中),因此所有必要的数据可能不再像以前那样仅通过一个查询来获取。

…事实并非如此,但我同意Darwen论文中没有明确提及外部加入问题;这是让我想要的一件事。明确的答案可以在日期的另一本书中找到……

首先,请注意日期和Darwen自己的真正关系语言 教程d 只有一种加入类型是自然的加入。理由是实际上只需要一种联接类型。

我提到的日期书是很棒的 SQL和关系理论:如何编写准确的SQL代码:

4.6:关于外部联盟的评论:“从关系上说,[外部加入是一种shot弹枪婚姻:它都会迫使桌子融入一种联盟 - 是的,我的意思是,我的意思是联合,即使是在有问题的桌子上没有的时候符合工会的通常要求...实际上,通过在联盟之前用一个或两个桌子填充一张或两个桌子来实现这一点不应用适当的值而不是零值完成

使用您的示例和默认值'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
                  );

达尔文的纸张专业人士说两个明确的桌子,说 BirthDateBirthDateKnown, ,但是SQL不会有太大不同 BirthDateKnown 代替半差异 BirthDate 多于。

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

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

Person JOIN BirthDate UNION Person NOT MATCHING BirthDate ADD '1900-01-01' AS DateOfBirth;

我还没有读过,但是有一篇文章叫 如何使用s-by-c处理丢失的信息第三宣言 Hugh Darwen和CJ Date运营的网站。这不是CJ日期撰写的,但我认为,由于它是该网站上的文章之一,它可能与他的观点相似。

一种选择是 实体 - 属性值 模型:

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

如果生日未知,您将省略其行。

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

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

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

您可以消除 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个参数).

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

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

这仅适用于您明确要求的那些属性,但没有 null愚蠢的三价逻辑。 Nothing == NothingTrue, , 不是 unknown 或者 null.

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

data EmploymentStatus = Employed EmployerID | Unemployed | Unknown

(当然,支持这一点的数据库也需要支持随之而来的比平常更复杂的外键约束。)

简而言之,我同意 APC'沙 当时大约6NF的答案。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top