mysql不允许在唯一索引上使用NULL

时间:2013-03-22 18:04:34

标签: php mysql sql oracle mysql-workbench

我使用mysql创建了一个表。 表的列是sequence,email,date_created和date_canceled。 primary是序列,我试图为电子邮件和date_canceled创建一个唯一索引 - 所以当电子邮件处于活动状态(从未被取消 - 意味着date_canceled为NULL)时,不会插入另一个有效的电子邮件或发生这种情况。

我知道它可以用oracle db完成,但是mysql唯一索引允许NULL。

任何建议如何处理? 谢谢!

2 个答案:

答案 0 :(得分:1)

我对您的问题的理解是,您希望每个用户随时都有一封与之关联的有效电子邮件,同时保留用户的历史记录。以前的邮件。

解决方案1 ​​

从MySQL 5.7.5开始,您可以通过创建生成的列,然后对其进行唯一约束来完成此操作; http://mysqlserverteam.com/generated-columns-in-mysql-5-7-5/。 例如

create table UniqueActiveEmailDemo 
(
    sequence bigint not null auto_increment primary key
  , userId bigint not null
  , email nvarchar(1024) not null
  , date_created timestamp default now()
  , date_cancelled datetime
  , single_active_mail_per_user bigint as (if(date_cancelled is null, userId, null))
);

alter table UniqueActiveEmailDemo 
add unique UK_UniqueActiveEmailDemo_SingleActiveMailPerUser 
(single_active_mail_per_user);

因为MySQL在唯一约束中允许多个空值,在取消记录的情况下,生成列具有空值;所以你可以拥有任意数量的记录。但是,如果记录未被取消,则生成的列将返回用户的ID;这取决于唯一约束,因此如果另一个具有相同用户ID的活动记录,则唯一约束将引发异常。

解决方案2

Sql Fiddle:http://sqlfiddle.com/#!9/b54dce/2

然而,与早期版本一起使用的更干净的方法是简单地防止date_cancelled可以为空;相反,对于尚未取消的任何项目,在远期将其设置为固定值,然后将user_id和date_cancelled的组合设为null;例如

create table UniqueActiveEmailDemo 
(
  sequence bigint not null auto_increment primary key
  , userId bigint not null
  , email nvarchar(1024) not null
  , date_created timestamp default now()
  , date_cancelled datetime not null default '9999-12-31 23:59:59'
);

ALTER TABLE UniqueActiveEmailDemo 
ADD UNIQUE UK_UniqueActiveEmailDemo_SingleActiveMailPerUser 
(userId, date_cancelled);
  • 这里的一个区别是你不能在同一天取消同一个用户的两条记录;但实际上我认为你无论如何也永远不会那样。
  • 这也意味着您可以拥有将来获得的记录。要避免此问题,您可以将9999-12-31 23:59:59视为空值;即任何记录都有该值是活跃的。
  • 您还可以通过在active_from上添加where now() between date_active and date_cancelled日期和过滤器来解决上述问题;但是,您需要添加更多检查以确保同一用户的活动窗口不重叠;这使事情变得更加复杂。
  • 添加检查约束可以防止未来的值;但遗憾的是,他们目前没有在MySQL中使用(尽管是有效的陈述)。 http://dev.mysql.com/doc/refman/5.7/en/create-table.html

, date_cancelled datetime not null default '9999-12-31 23:59:59' check (date_cancelled = '9999-12-31 23:59:59' or date_cancelled <= now())

答案 1 :(得分:0)

BDB存储引擎会将NULL视为唯一值,只允许一个NULL值。

From the docs

  

如果允许NULL值的列具有唯一索引,则只有一个   允许NULL值。这与其他存储引擎不同,   允许在唯一索引中使用多个NULL值。

如果您不想更改存储引擎以获得此行为,那么您的其他选项是更改表结构以存储活动电子邮件并取消不同表中的电子邮件...创建触发器强制执行此行为...或为active电子邮件分配魔术日期值,并且不再允许此列中的NULL值。