在唯一列中允许null

时间:2013-11-22 20:36:47

标签: sql postgresql database-design null 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
 );

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

我怎样才能做到这一点?

6 个答案:

答案 0 :(得分:65)

这是一种误解 UNIQUE约束完全您想要的内容。多个NULL值可以在定义为UNIQUE的列中共存。

引用the manual about UNIQUE constraints

  

通常,当超过时,会违反唯一约束   表中包含所有列的值的一行   在约束中是相等的。但是,两个空值不是   在这个比较中被认为是平等这意味着即使在场   一个唯一约束,可以存储重复的行   在至少一个受约束列中包含空值。这个   行为符合SQL标准,但我们听说过其他   SQL数据库可能不遵循此规则。所以小心的时候   开发旨在便携的应用程序。

大胆强调我的。

请注意character types允许空字符串(''), NULL值,并且会像其他任何内容一样触发唯一的违规行为在多行中输入时为非空值。

答案 1 :(得分:15)

Postgres中没有这样的问题

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

其他数据库的解决方法

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

UUID

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

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命令。有关说明,请参阅installing in Postgres 9.1 and latermy other post on Postgres 9.0 and earlier上的博文。如果扩展/插件已编译并与Postgres安装捆绑在一起,则新旧安装方式都很简单。

摘要

让我明确一点,仅对于Postgres,不需要此解决方法,因为Postgres符合SQL标准。但是如果:

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

...然后需要这样的解决方法。

答案 2 :(得分:5)

某些数据库不允许多个空值,例如SQL Server documentation状态“多个空值被视为重复”。在不允许可以为空的UNIQUE约束的数据库上,你可以尝试这个(从GuidoG's answer到另一个问题):

CREATE UNIQUE NONCLUSTERED INDEX IDX_Email
ON MMCompany (Email)
WHERE Email IS NOT NULL;

答案 3 :(得分:3)

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

CREATE TABLE CompanyEmail
 (
    CompanyUniqueID INT NOT NULL PRIMARY KEY
       REFERENCES MMCompany (CompanyUniqueID),
    Email VARCHAR(75) NOT NULL UNIQUE
 );

避免可以为空的UNIQUE约束。

答案 4 :(得分:2)

唯一和null不相处太多,因为null定义未定义 - 你不知道两个空是否是同一个未知。

从这个意义上讲,您当前对电子邮件的唯一约束是正确的,应该按原样运作。


如果您需要另外制作,则部分索引有效:

create unique index on MMCompany((email is null)) where (email is null);

另一种方法是定义约束触发器。类似的东西:

create function email_chk() returns trigger as $$
begin
  if exists (
    select 1 from mmcompany where email is null and companyuniqueid <> new.id
  ) then
    raise 'dup null found';
  end if;
  return null;
end;
$$ language plpgsql;

create constraint trigger after insert or update on mmcompany
for each row when (new.email is null)
execute procedure email_chk();

答案 5 :(得分:-1)

如果您使用EF Code First生成数据库表,请编辑您的迁移类&#39;按以下方法强制执行UNIQUE KEY约束以忽略NULL。

migrationBuilder.Sql(@"CREATE UNIQUE NONCLUSTERED INDEX[IX_Employees_TaskId] ON[dbo].[Employees]([TaskId] ASC)
                                WHERE [TaskId] IS NOT NULL"
                                );

然后,您可以通过SQL Server Management Studio或类似的东西登录您的数据库来测试您的唯一约束。就像在这种情况下一样,Employee Table在TaskId中愉快地接受2个NULL值,尽管它是一个UNIQUE列。

enter image description here