插入新记录时如何根据列值忽略多列上的唯一索引
How to ignore unique Index on multiple columns based on column value while inserting new record
我们有一个包含三列的 table,StudentId
、SubjectId
和 Active
(以及其他一些列,但与此问题无关)。
Active
列指示记录是否处于活动状态(如果有人从 UI 中删除记录,我们将此 Active
列设置为零)
StudentId
和SubjectId
列的索引定义如下:
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
在我们的应用程序中,如果我们尝试使用此 SubjectId
和 studentId
的组合插入另一条记录,则插入失败并且 Java 抛出以下错误:
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Cannot
insert duplicate key row in object 'dbo.StudentSubject' with unique
index 'UQ_StudentSubject_SubjectId_StudentId'. The duplicate key value
is (113460, 182).
组合为 113460、182 的记录的 Active 为零,因此我们正在尝试插入新记录而不是将 Active 标志设置为 1。
如果我们插入一条新记录,现有的 subjectId 和 studentId 组合的 Active
列为零,那么有什么方法可以在插入时忽略此索引?
编辑
很抱歉造成混淆,它是一个索引而不是一个约束。
下面是过滤索引的示例,它将忽略活动值为 0 的行:
CREATE UNIQUE NONCLUSTERED INDEX UQ_StudentSubject_SubjectId_StudentId
ON dbo.StudentSubject
(
[StudentId],
[SubjectId]
) WHERE Active <> 0;
您可能不想将 Active 添加到索引中,但如果您希望拥有两个(或更多)版本的 (studentID, subjectID),只是 Active 的值不同,那么您已经设计得很好自己到那个角落。如果您需要不止一行具有 StudentID 和 SubjectID 的特定组合,您将不得不更改设计。对不起。
一个选项(如果可用)是通过时间戳添加一行 "unique-ifier"。
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!
要获取当前的活动设置,只需获取具有最新 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 );
不要让子查询让你担心。注意它只使用聚簇索引。
这里有一些额外的好处。假设你被问到,"What was the Active value of student 100 and subject 42 as of 2015-01-04?" 好吧,你只需将这一行添加到查询的末尾,作为子查询的一部分:
and ss1.ChangeDate <= @AsOfDate );
并将@AsOfDate 设置为您要查看的日期。
这为您提供了一个 "lookback" 查询来查看数据在任何特定时间的样子。事实上,将 @AsOfDate 设置为 GetDate()
将 return 当前状态,因此您可以对 "current" 和 "lookback" 查询使用相同的查询。
另一个可能的好处:假设您提前知道 Active 值会发生变化。今天是 2015 年 1 月 10 日,该行计划在 2015 年 1 月 15 日停用。继续并插入具有预定日期的行:
100 42 2015-01-15 0 ...
"current" 查询将继续将 Active 值显示为 1 直到 15 日,届时该值将显示为 0。
仅显示当前版本的视图使应用程序可以使用该数据,因此它甚至不必了解结构。视图上的触发器将使应用程序可以在一个对象上查询和执行 DML,为您提供应用程序和数据之间的抽象层。
我们有一个包含三列的 table,StudentId
、SubjectId
和 Active
(以及其他一些列,但与此问题无关)。
Active
列指示记录是否处于活动状态(如果有人从 UI 中删除记录,我们将此 Active
列设置为零)
StudentId
和SubjectId
列的索引定义如下:
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
在我们的应用程序中,如果我们尝试使用此 SubjectId
和 studentId
的组合插入另一条记录,则插入失败并且 Java 抛出以下错误:
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Cannot insert duplicate key row in object 'dbo.StudentSubject' with unique index 'UQ_StudentSubject_SubjectId_StudentId'. The duplicate key value is (113460, 182).
组合为 113460、182 的记录的 Active 为零,因此我们正在尝试插入新记录而不是将 Active 标志设置为 1。
如果我们插入一条新记录,现有的 subjectId 和 studentId 组合的 Active
列为零,那么有什么方法可以在插入时忽略此索引?
编辑 很抱歉造成混淆,它是一个索引而不是一个约束。
下面是过滤索引的示例,它将忽略活动值为 0 的行:
CREATE UNIQUE NONCLUSTERED INDEX UQ_StudentSubject_SubjectId_StudentId
ON dbo.StudentSubject
(
[StudentId],
[SubjectId]
) WHERE Active <> 0;
您可能不想将 Active 添加到索引中,但如果您希望拥有两个(或更多)版本的 (studentID, subjectID),只是 Active 的值不同,那么您已经设计得很好自己到那个角落。如果您需要不止一行具有 StudentID 和 SubjectID 的特定组合,您将不得不更改设计。对不起。
一个选项(如果可用)是通过时间戳添加一行 "unique-ifier"。
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!
要获取当前的活动设置,只需获取具有最新 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 );
不要让子查询让你担心。注意它只使用聚簇索引。
这里有一些额外的好处。假设你被问到,"What was the Active value of student 100 and subject 42 as of 2015-01-04?" 好吧,你只需将这一行添加到查询的末尾,作为子查询的一部分:
and ss1.ChangeDate <= @AsOfDate );
并将@AsOfDate 设置为您要查看的日期。
这为您提供了一个 "lookback" 查询来查看数据在任何特定时间的样子。事实上,将 @AsOfDate 设置为 GetDate()
将 return 当前状态,因此您可以对 "current" 和 "lookback" 查询使用相同的查询。
另一个可能的好处:假设您提前知道 Active 值会发生变化。今天是 2015 年 1 月 10 日,该行计划在 2015 年 1 月 15 日停用。继续并插入具有预定日期的行:
100 42 2015-01-15 0 ...
"current" 查询将继续将 Active 值显示为 1 直到 15 日,届时该值将显示为 0。
仅显示当前版本的视图使应用程序可以使用该数据,因此它甚至不必了解结构。视图上的触发器将使应用程序可以在一个对象上查询和执行 DML,为您提供应用程序和数据之间的抽象层。