我有一个bit IsDefault
列。表中只有一行数据可能将此位列设置为1
,其他所有数据都必须为0
。
我该如何强制执行此操作?
答案 0 :(得分:32)
所有版本:
SQL Server 2008:a filtered index
CREATE UNIQUE INDEX IX_foo ON bar (MyBitCol) WHERE MyBitCol = 1
答案 1 :(得分:18)
假设您的PK是单个数字列,您可以在表中添加计算列:
ALTER TABLE YourTable
ADD IsDefaultCheck AS CASE IsDefault
WHEN 1 THEN -1
WHEN 0 THEN YourPK
END
然后在计算列上创建唯一索引。
CREATE UNIQUE INDEX IX_DefaultCheck ON YourTable(IsDefaultCheck)
答案 2 :(得分:2)
我认为如果您想在插入/更新新记录时将旧的默认记录更改为0,并且您想确保一条记录始终具有该值(即,如果您删除记录),则触发器是最佳选择使用您将其分配给不同记录的值)。你必须决定这样做的规则。这些触发器可能很棘手,因为您必须在插入和删除的表中考虑多个记录。因此,如果批处理中的3条记录尝试更新成为默认记录,哪一条胜出?
如果你想确保当其他人试图改变它时,一个默认记录永远不会改变,过滤后的索引是个好主意。
答案 3 :(得分:1)
您可以应用“替代插入”触发器并检查其进入的值。
Create Trigger TRG_MyTrigger
on MyTable
Instead of Insert
as
Begin
--Check to see if the row is marked as active....
If Exists(Select * from inserted where IsDefault= 1)
Begin
Update Table Set IsDefault=0 where ID= (select ID from inserted);
insert into Table(Columns)
select Columns from inserted
End
End
或者,您可以对列应用唯一约束。
答案 4 :(得分:1)
以下问题的接受答案既有趣又相关:
Constraint for only one record marked as default
"但严肃的关系人员会告诉你这些信息 应该只是在另一张桌子里。"
有一个单独的1行表,告诉你哪个记录是'默认'。 Anon在评论中谈到了这一点。
我认为这是最好的方法 - 简单,干净&并不需要一个聪明的'深奥的解决方案容易出错或以后的误解。您甚至可以删除IsDefualt
列。
答案 5 :(得分:1)
这里可以采取不同的方法,但我认为只有两种方法是正确的。但让我们一步一步来做。
我们有表 Hierachy 表,其中有根列。此列告诉我们当前哪一行是起点。如问题所述,我们希望只有一个起点。
我们认为我们可以做到:
<强>约束强>
在这种方法中,首先我们需要创建能够完成工作的功能。
CREATE FUNCTION [gt].[fnOnlyOneRoot]()
RETURNS BIT
BEGIN
DECLARE @rootAmount TINYINT
DECLARE @result BIT
SELECT @rootAmount=COUNT(1) FROM [gt].[Hierarchy] WHERE [Root]=1
IF @rootAmount=1
set @result=1
ELSE
set @result=0
RETURN @result
END
GO
然后是约束:
ALTER TABLE [gt].[Hierarchy] WITH CHECK ADD CONSTRAINT [ckOnlyOneRoot] CHECK (([gt].[fnOnlyOneRoot]()=(1)))
不幸的是,方法是错误的,因为此约束不允许我们更改表中的任何值。它需要准确地标记一个根(插入Root = 1将抛出异常,并使用set Root = 0进行更新)
我们可以更改 fnOnyOneRoot 以允许选择0个根但不是我们想要的。
<强>索引强>
索引将删除where子句中定义的所有行,其余数据将设置唯一约束。我们有不同的选择: - Root可以为空,我们可以在Root!= 0和Root不为null的地方添加 - Root必须有值,我们只能在Root!= 0的地方添加 - 和不同的组合
CREATE UNIQUE INDEX ix_OnyOneRoot ON [gt].[Hierarchy](Root) WHERE Root !=0 and Root is not null
这种方法也不完美。将强制最多一个Root,但最小不是。要更新数据,我们需要将以前的行设置为null或0.
<强>触发强>
我们可以做两种触发,两者表现不同 - 防止触发 - 这将不允许我们输入错误的数据 - DoTheJob触发器 - 后台将为我们更新数据
防止触发
如果我们只想强制一个root而不是更新或插入,那么这与约束基本相同。
CREATE TRIGGER tOnlyOneRoot
ON [gt].[Hierarchy]
AFTER INSERT, UPDATE
AS
DECLARE @rootAmount TINYINT
DECLARE @result BIT
SELECT @rootAmount=COUNT(1) FROM [gt].[Hierarchy] WHERE [Root]=1
IF @rootAmount=1
set @result=1
ELSE
set @result=0
IF @result=0
BEGIN
RAISERROR ('Only one root',0,0);
ROLLBACK TRANSACTION
RETURN
END
GO
DoTheJob触发器
此触发器将检查所有插入/更新的行,如果将传递多个Root,则会抛出异常。在其他情况下,如果一个新的Root将被更新或插入,触发器将允许执行它,并且在操作之后它将所有其他行的Root值更改为0.。
CREATE TRIGGER tOnlyOneRootDoTheJob
ON [gt].[Hierarchy]
AFTER INSERT, UPDATE
AS
DECLARE @insertedCount TINYINT
SELECT @insertedCount = COUNT(1) FROM inserted WHERE [Root]=1
if (@insertedCount > 1)
BEGIN
RAISERROR ('Only one root',0,0);
ROLLBACK TRANSACTION
RETURN
END
DECLARE @newRootId INT
SELECT @newRootId = [HierarchyId] FROM inserted WHERE [Root]=1
UPDATE [gt].[Hierarchy] SET [Root]=0 WHERE [HierarchyId] <> @newRootId
GO
这是我们试图实现的解决方案。始终只有一个根规则符合。 (应该进行删除的附加触发器)
不同的表和关系
这可以说是更规范化的方式。我们创建新表只允许有一行(使用上面描述的选项)并且我们加入。
CREATE TABLE [gt].[HierarchyDefault](
[HierarchyId] INT PRIMARY KEY NOT NULL,
CONSTRAINT FK_HierarchyDefault_Hierarchy FOREIGN KEY (HierarchyId) REFERENCES [gt].[Hierarchy](HierarchyId)
)
它会达到性能吗?
有一栏
SET STATISTICS TIME ON;
SELECT [HierarchyId],[ParentHierarchyId],[Root]
FROM [gt].[Hierarchy] WHERE [root]=1
SET STATISTICS TIME OFF;
结果 CPU时间= 0 ms,经过时间= 0 ms。
加入:
SET STATISTICS TIME ON;
SELECT h.[HierarchyId],[ParentHierarchyId],[Root]
FROM [gt].[Hierarchy] h
INNER JOIN [gt].[HierarchyDefault] hd on h.[HierarchyId]=hd.[HierarchyId]
WHERE [root]=1
SET STATISTICS TIME OFF;
结果 CPU时间= 0 ms,经过时间= 0 ms。
<强>摘要强> 我会用触发器。表格中有些神奇,但它完成了所有工作。
轻松创建表格:
CREATE TABLE [gt].[Hierarchy](
[HierarchyId] INT PRIMARY KEY IDENTITY(1,1),
[ParentHierarchyId] INT NULL,
[Root] BIT
CONSTRAINT FK_Hierarchy_Hierarchy FOREIGN KEY (ParentHierarchyId)
REFERENCES [gt].[Hierarchy](HierarchyId)
)