不久前,我一直在阅读这本书SQL and Relational Theory by C. J. Date。作者因批评SQL的三值逻辑(3VL)而闻名。 1)
作者强调了为什么在SQL中应该避免使用3VL,但是他没有概述如果不允许可以为空的列,数据库模型会是什么样子。我已经考虑了一点,并提出了以下解决方案。如果我错过了其他设计方案,我想听听他们的意见!
1) 日期对SQL 3VL的批评反过来也受到了批评:见this paper by Claude Rubinson(包括CJ Date的原始批评)。
示例表
例如,请使用下表,我们有一个可为空的列(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
(因为它们会将NULL
引入结果集中),因此可能不再像以前那样只使用一个查询来获取所有必需的数据。
问题:
是否有其他选项可以消除NULL
(如果是,它们是什么)?
答案 0 :(得分:4)
我看到Date的同事Hugh Darwen在一篇精彩的演讲“如何处理不使用NULL的信息”中讨论了这个问题,这可以在the Third Manifesto website上找到。
他的解决方案是第二种方法的变体。它是第六种常规形式,其中包含用于保存出生日期和未知标识符的表格:
# +-----------------------------+ 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's INSERT ALL syntax。
答案 1 :(得分:2)
我建议你选择2.我非常肯定Chris Date也会因为你正在做的事情完全正常化6NF,这是Date was jointly responsible for introducing最高的正常形式。 我推荐Darwen's paper处理缺失信息。
由于不允许OUTER JOIN(因为它们会引入NULL) 进入结果集),可能不再需要所有必要的数据 像以前一样只用一个查询获取。
......事实并非如此,但我同意Darwen文件中没有明确提到外连接的问题;这是让我想要的一件事。明确的答案可以在Date的另一本书中找到......
首先,请注意Date和Darwen自己的真正关系语言Tutorial D只有一种连接类型是自然连接。理由是实际上只需要一种连接类型。
我提到的日期书是优秀的SQL and Relational Theory: How to Write Accurate SQL Code:
4.6:关于外部联接的评论:“从关系上讲,[外部联接]是a 一种猎枪婚姻:它迫使桌子成为一种联盟 - 是的,我 表示联盟,而不是加入 - 即使有问题的表没有 符合工会的通常要求......它是这样做的 在执行之前,通过使用空值填充一个或两个表来实现效果 结合,从而使它们符合那些通常的要求 毕竟。但是没有理由不应该填充填充 使用适当的值而不是空值
使用您的示例和默认值“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的论文提出了两个明确的表格,比如BirthDate
和BirthDateKnown
,但SQL不会有太大不同,例如:半连接到BirthDateKnown
代替上面BirthDate
的半差异。
请注意,上述内容仅使用JOIN
和INNER JOIN
,因为标准SQL-92 NATURAL JOIN
和UNION CORRESPONDING
并未在现实生活中的SQL产品中广泛实现(无法找到引用但是IIRC Darwen主要负责后两者进入标准。
进一步注意,上面的语法看起来很啰嗦,因为SQL一般都是啰嗦。在纯关系代数中,它更像是(伪代码):
Person JOIN BirthDate UNION Person NOT MATCHING BirthDate ADD '1900-01-01' AS DateOfBirth;
答案 2 :(得分:1)
我还没看过,但有一篇名为如何使用S-by-C处理丢失信息的文章在由Hugh Darwen和CJ Date运营的Third Manifesto网站上。 这不是由C.J.Date写的,但我认为因为它是该网站上的一篇文章,它可能与他的观点相似。
答案 3 :(得分:0)
一种替代方案可能是entity-attribute-value模型:
entity attribute value
1 name Banana Man
1 birthdate 1968-06-20
如果生日是未知的,你只需省略它的行。
答案 4 :(得分:0)
选项3:记录作者的负责人:
CREATE TABLE Person
(
PersonId int PRIMARY KEY IDENTITY(1,1),
Name nvarchar(100) NOT NULL,
DateOfBirth datetime NOT NULL
)
为什么要扭曲模型以在您的目标是消除它们时允许空表示?
答案 5 :(得分:0)
您可以使用COALESCE
删除输出中的null
。
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个参数)。
答案 6 :(得分:0)
一种选择是使用显式option types,类似于Haskell的Maybe
仿函数。
不幸的是,很多现有的SQL实现对用户定义的代数数据类型的支持很少,甚至对用户定义的类型构造函数的支持也更差,你真的需要干净利落地完成这些工作。
这只为那些明确要求它的属性恢复了一种“null”,但没有null
愚蠢的三值逻辑。 Nothing == Nothing
为True
,不是unknown
或null
。
当有几个缺少信息的原因时,对用户定义的代数类型的支持也会有所帮助,例如,等效于以下Haskell类型的数据库对于明显的应用程序来说是一个很好的解决方案:
data EmploymentStatus = Employed EmployerID | Unemployed | Unknown
(当然,支持这一点的数据库也需要支持随之而来的更复杂的外键约束。)
如果没有这个,我同意APC和onedaywhen关于6NF的答案。