【问题标题】:Unique constraint for table with column for soft delete具有软删除列的表的唯一约束
【发布时间】:2020-06-08 10:16:15
【问题描述】:

我有一个带有列的表格:

Id int
Name varchar *
Description varchar
LevelId int *
DeletedAt datetime nullable *

我想对上面标有星号的字段设置唯一约束:Name、LevelId、DeletedAt。我为约束添加 DeletedAt 的原因是,当有人软删除并添加具有相同名称和 LevelId 的新记录时,数据库将允许添加。但是我错误地认为不允许有 2 行具有相同的 Name、LevelId 和 DeletedAt 的 NULL,因为 NULL 不等于 NULL。

我需要的是一个替代方案。我怎样才能支持这个要求?我能想到的一件事是用 varchar 替换 DeletedAt,然后它有一个默认值,例如“Active”或一个空字符串(只是不为空),然后将日期作为已删除行的字符串。但我在想是否有更优雅的解决方案。

【问题讨论】:

  • 在 SQL Server 中,唯一约束中只允许有一个 NULL,完全符合您的要求。
  • 参见例如this question 这只是人们寻求您(并非不合理地)假设已经是这种情况的众多行为之一。
  • 我使用的是 SQL Server 2014,在上面的架构中,我可以在 DeletedAt 上输入 2 条具有相同名称、LevelId 和 NULL 的记录。我需要的是不允许这样做。我最初认为这是不允许的,因为我认为 NULL 将等于 NULL,但它并不限制我为 2 条记录输入相同的值。我需要一些东西来替换可为空的 DeletedAt 列。
  • 我根据您上面的描述在 SQL Server 2014 中创建了一个表,并尝试输入两行匹配的名称、描述和 DeletedAt。错误信息是Msg 2601, Level 14, State 1, Line 1 Cannot insert duplicate key row in object 'dbo.T' with unique index 'IX_T'. The duplicate key value is (abc, def, <NULL>).
  • 事实上,这甚至在 documentation 中都有说明 - "... UNIQUE 约束允许值 NULL。但是,与参与 UNIQUE 的任何值一样约束,每列只允许一个空值"

标签: sql-server database-design


【解决方案1】:

我喜欢为此使用唯一的过滤索引。对于您的具体情况,它看起来像:

create unique filtered index FUIX_Name_LevelId
   on dbo.yourTable (Name, LevelID)
   where DeletedAt is null;

这将只允许每个 (Name, LevelId) 元组有一个“活动”行。它还允许尽可能多的“已删除”记录(因为这些行不符合索引过滤器的条件,因此在确定唯一性时不会考虑它们)。

我过去一直被咬的一件事是:具有过滤索引的表需要某些查询设置才能如此,否则对表的查询将失败。有关更多信息,请参阅过滤索引上的 the documentation

【讨论】:

  • 谢谢 Ben,我会检查一下,看看它是否适合我。
【解决方案2】:

创建 3 列的唯一复合键。 第一个是您的唯一列,第二个是 LevelId,Last 是 DeletedAt(任何删除参数都可以是删除时间或数值 - 如果删除则增量值,如果未软删除则为 0)

CREATE UNIQUE NONCLUSTERED INDEX TABLE_NAME_DELETED_AT_NonClusteredIndex ON 
TABLE (
Name, LevelId, DeletedAt
) 
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

【讨论】:

    【解决方案3】:

    我遇到了相同的情况,但需要使用两个条件进行过滤。但是过滤后的索引不支持OR条件,但支持AND运算符。

    如果任何人面临相同的场景,他们可以使用 IN 运算符来实现想要的。

    例如:

    create unique index FUIX_Name_LevelId
    on dbo.yourTable (Name, LevelID)
    where DeletedAt = 1 OR DeletedAt = 0; -- this will give an error.
    
    --solution
    create unique index FUIX_Name_LevelId
    on dbo.yourTable (Name, LevelID)
    where DeletedAt in (1, 0); 
    

    【讨论】:

      猜你喜欢
      • 2020-01-27
      • 2011-03-30
      • 2012-10-26
      • 1970-01-01
      • 2019-06-15
      • 2012-04-18
      • 1970-01-01
      • 2017-02-23
      • 2011-07-26
      相关资源
      最近更新 更多