【问题标题】:How to ignore unique Index on multiple columns based on column value while inserting new record插入新记录时如何根据列值忽略多列上的唯一索引
【发布时间】:2015-09-30 01:00:26
【问题描述】:

我们有一个包含三列的表,StudentIdSubjectIdActive(以及其他一些列,但与此问题无关)。

Active 列指示记录是否处于活动状态(如果有人从 UI 中删除记录,我们将此 Active 列设置为零)

StudentIdSubjectId 列的索引定义如下:

CREATE UNIQUE NONCLUSTERED INDEX [UQ_StudentSubject_SubjectId_StudentId]  
ON [dbo].[StudentSubject]
(
    [StudentId] ASC,
    [SubjectId] ASC

)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]

GO

在我们的应用程序中,如果我们尝试使用 SubjectIdstudentId 的组合插入另一条记录,则插入失败并且 Java 抛出以下错误:

原因:com.microsoft.sqlserver.jdbc.SQLServerException: 不能 在具有唯一性的对象“dbo.StudentSubject”中插入重复的键行 索引“UQ_StudentSubject_SubjectId_StudentId”。重复键值 是 (113460, 182)。

组合为 113460、182 的记录的 Active 为零,因此,我们尝试插入新记录,而不是将 Active 标志设置为 1。

如果我们插入一条新记录,现有的 subjectId 和 studentId 组合的 Active 列为零,有什么方法可以在插入时忽略此索引?

编辑 对不起,这是一个索引而不是一个约束。

【问题讨论】:

  • 在索引中添加Active 列会解决这个问题吗?
  • 没有唯一约束,但您可以创建一个指定WHERE Active = 0的唯一过滤索引。
  • 我们不想将活动列添加到索引中,问题是如果我们将来不活动该列,将会有两列具有相同的组合,因此更新可能会失败。 @FelixPamittan
  • @DanGuzman 我的上述评论有意义吗?
  • 我对过滤索引一无所知。将谷歌它。 @FelixPamittan

标签: sql sql-server database database-design unique-constraint


【解决方案1】:

以下是过滤索引的示例,它将忽略 Active 值为 0 的行:

CREATE UNIQUE NONCLUSTERED INDEX UQ_StudentSubject_SubjectId_StudentId  
ON dbo.StudentSubject
(
    [StudentId],
    [SubjectId]
) WHERE Active <> 0;

【讨论】:

  • 太棒了!这就是我要找的。​​span>
  • 有没有办法通过 EF 代码优先的方法来定义这样的索引?
  • @Raj,看起来您需要首先使用带有 EF 代码的 DbMigration 类,如answer 中所述。
【解决方案2】:

您可能不想将 Active 添加到索引中,但如果您希望有两个(或更多)版本的 (studentID, subjectID),仅在 Active 的值上有所不同,那么您几乎已经将自己设计为那个角落。如果您需要多行包含 StudentID 和 SubjectID 的特定组合,您将不得不更改设计。对不起。

如果您可以使用,一个选项是通过时间戳添加一行“唯一标识符”。

create table StudentSubject(
    StudentID   int not null,
    SubjectID   int not null,
    ChangeDate  datetime2 default GetDate(),
    Active      int default 1,
    ...,
    constraint  PK_StudentSubject primary key( StudentID, SubjectID, ChangeDate )
);

现在您有了一个无限可扩展的解决方案——任何特定的学生和科目配对都可以有任意数量的活动设置。

StudentID SubjectID ChangeDate Active ...
      100        42 2015-01-01      1 ... -- first entry
      100        42 2015-01-03      0 ... -- nope, deactivate this entry
      100        42 2015-01-05      1 ... -- changed my mind, reactivate
      100        42 2015-01-07      0 ... -- horoscope indicates bad idea
      100        42 2015-01-09      1 ... -- wait, I'm not superstitious, go for it!

要获取当前的 Active 设置,只需获取具有最新 ChangeDate 值的记录:

select  ss.StudentID, ss.SubjectID, ss.ChangeDate, ss.Active
from    StudentSubject  ss
where   ss.StudentID = @Stud
    and ss.SubjectID = @Subj
    and ss.ChangeDate =(
        select  Max( ss1.ChangeDate )
        from    StudentSubject  ss1
        where   ss1.StudentID = ss.StudentID
            and ss1.SubjectID = ss.SubjectID );

不要让子查询关心你。注意它只使用聚集索引。

这里有几个额外的好处。假设你被问到,“截至 2015 年 1 月 4 日,学生 100 和科目 42 的 Active 值是多少?”好吧,您只需将此行添加到查询的末尾,作为子查询的一部分:

            and ss1.ChangeDate <= @AsOfDate );

并将@AsOfDate 设置为您想查看的日期。

这为您提供“回溯”查询,以查看数据在任何特定时间的样子。实际上,将@AsOfDate 设置为GetDate() 将返回当前状态,因此您可以对“当前”和“回溯”查询使用相同的查询。

另一个可能的好处:假设您提前知道 Active 值会发生变化。今天是 2015 年 1 月 10 日,该行计划在 2015 年 1 月 15 日停用。继续插入带有预定日期的行:

      100        42 2015-01-15      0 ...

“当前”查询将继续将 Active 值显示为 1,直到 15 日,该值将显示为 0。

仅显示当前版本的视图使应用程序可以使用该数据,因此它甚至不必了解结构。视图上的触发器将使应用程序可以在一个对象上查询和执行 DML,从而为您提供应用程序和数据之间的抽象层。

【讨论】:

    猜你喜欢
    • 2016-02-22
    • 2011-12-03
    • 1970-01-01
    • 2012-04-17
    • 1970-01-01
    • 2013-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多