在唯一列中允许null

liv*_*v a 35 sql postgresql null database-design unique-constraint

我创建了下表:

CREATE TABLE MMCompany (
   CompanyUniqueID BIGSERIAL PRIMARY KEY NOT NULL, 
   Name VARCHAR (150) NOT NULL,
   PhoneNumber VARCHAR(20) NOT NULL UNIQUE, 
   Email VARCHAR(75) UNIQUE,
   CompanyLogo BYTEA
 );
Run Code Online (Sandbox Code Playgroud)

电子邮件列是唯一的,它在我的方案中导致"错误",因为只有一个记录为null.我试图获得没有相同电子邮件的公司记录,但同时允许公司没有电子邮件.

我怎样才能做到这一点?

Erw*_*ter 72

这是一种误解.
UNIQUE约束不正是你想要的.多个NULL值可以在定义的列中共存UNIQUE.

引用有关UNIQUE约束的手册:

通常,当表中有多行时,约束中包含的所有列的值相等,则违反了唯一约束.但是,在此比较中,两个空值不相等.这意味着即使存在唯一约束,也可以在至少一个约束列中存储包含空值的重复行.此行为符合SQL标准,但我们听说其他SQL数据库可能不遵循此规则.因此在开发可移植的应用程序时要小心.

大胆强调我的.

请注意,字符类型允许空字符串(''),这是不是一个NULL值,在多行输入的时候会触发一个独特违反就像任何其他非空值.

  • @sqlvogel:我强调该功能的*有用性*(虽然它可以在不需要时轻松关闭)。您在 OP 确切要求的问题下写了“这样做没有用”,*证明*其有用性?你失去了讽刺吗? (4认同)
  • @sqlvogel:要避免该功能,请定义列"NOT NULL".这两个世界中最好的,没什么难看的.有用,因为这个要求去证明.所以,显然,我不完全同意你的判决. (3认同)
  • @sqlvogel 和 ErwinBrandstetter 看起来您已经陷入关于 NULL 的价值或危险性的古老辩论中。对于 NULL 的详尽评论,请阅读 [Dr. Chris Date](http://en.wikipedia.org/wiki/Christopher_J._Date) 的书,[SQL 标准指南](http://www.amazon.com/Guide-SQL-Standard-4th-版/dp/0201964260)。您还可以阅读学术文章,这些文章解释了 NULL 可能具有的多种含糊不清的含义。虽然我认为 NULL 比帮助更麻烦,但其他人却找到了实际用途。我建议就此放手。 (3认同)
  • @foibs:这是100%正确和故障安全,符合SQL标准以及它应该如何.出于某种原因,`NULL`被定义为这种方式. (2认同)
  • 我完全赞成规范化,只要它是明智的一半。但我看到*没有*“关键依赖项”,也没有任何违反任何正常形式的行为。你仍然没有说出一个名字。不管怎样,这纯粹是学术性的。只要没有其他要求(例如每个电子邮件地址的属性或每个公司的多个电子邮件),为电子邮件属性创建单独的表是不明智的。可空列完全符合要求。我建议我们同意不同意。 (2认同)

Bas*_*que 18

Postgres中没有这样的问题

在Erwin Brandstetter的正确答案中,他解释说你确实应该看到你想要的行为(在Unique约束中允许多个NULL).您应该特别在Postgres中看到此行为以及任何符合SQL标准的数据库.

其他数据库的解决方法

但是,Postgres doc警告可移植性,因为已知某些数据库违反了此功能.对于这种不兼容的系统,我建议用伪造的值替换这些字段中NULL值的使用.虚假值将是一个字符串,例如"unknown_"加上一些几乎肯定是唯一的任意值.任意值可能类似于当前日期时间加上随机数.

UUID

但是,不是滚动自己的任意值,而是生成UUID.原始版本1 UUID确实是当前日期时间,随机数和计算机的几乎唯一MAC地址的组合.

UUID呈现为带有使用连字符的规范格式的十六进制字符串,如下所示:

93e6f268-5c2d-4c63-9d9c-40e6ac034f88

所以我的建议是组合一个任意字符串,如"unknown_"加上一个UUID,看起来像这样:

unknown_93e6f268-5c2d-4c63-9d9c-40e6ac034f88

因此,我对不兼容数据库的建议是生成这样的值并使用它代替NULL,在特定行的该列中尚未具有已知值的情况下使用它.而不是编写查找在该列中具有(或没有)NULL值的行的查询,而是编写查询以查找具有(或没有)以任意字符串开头的值的行,在此处使用"unknown_"例.然后每行将满足具有唯一值的约束.

实际上,我会将此"unknown_"+ UUID值指定为该列的默认值.

您还可以向此列添加NOT NULL约束.

生成UUID值

Postgres内置了对UUID数据类型的支持,但这里的答案与此无关.您需要的是生成UUID .

要生成UUID,您需要一个扩展(插件),将此功能添加到Postgres.大多数Postgres安装程序都包含此类扩展.这个扩展名为uuid-ossp.通常,默认情况下不会激活扩展名.要在最新版本的Postgres中执行此操作,请使用CREATE EXTENSION命令.有关说明,请参阅我在Postgres 9.1及更高版本中安装的博客文章或Postgres 9.0及更早版本中的其他帖子.如果扩展/插件已编译并与Postgres安装捆绑在一起,则新旧安装方式都很简单.

摘要

让我明确一点,仅对于Postgres,就没有必要采用这种解决方法,因为Postgres符合SQL标准.但如果:

  • 您担心代码可以移植到其他一些不合规的数据库系统,或者
  • 您需要与不合规的数据库系统交换数据,或
  • 你同意Chris Date博士的观点,即NULL是魔鬼的工作,应该避免

...然后这样的解决方法是必要的.


dum*_*dad 6

一些数据库不允许多个空值,例如SQL Server文档指出“多个空值被视为重复”。在不允许可空UNIQUE约束的数据库上,您可以尝试以下操作(来自GuidoG对另一个问题的回答):

CREATE UNIQUE NONCLUSTERED INDEX IDX_Email
ON MMCompany (Email)
WHERE Email IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)


nvo*_*gel 5

从表中删除电子邮件列。将它放在一个新表中,它可以是 NOT NULL 和 UNIQUE:

CREATE TABLE CompanyEmail
 (
    CompanyUniqueID INT NOT NULL PRIMARY KEY
       REFERENCES MMCompany (CompanyUniqueID),
    Email VARCHAR(75) NOT NULL UNIQUE
 );
Run Code Online (Sandbox Code Playgroud)

避免可为空的 UNIQUE 约束。