SQL Server位列约束,1行= 1,其他所有0

时间:2011-01-26 22:09:21

标签: sql-server

我有一个bit IsDefault列。表中只有一行数据可能将此位列设置为1,其他所有数据都必须为0

我该如何强制执行此操作?

6 个答案:

答案 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 表,其中有列。此列告诉我们当前哪一行是起点。如问题所述,我们希望只有一个起点。

simple hierarchy table

我们认为我们可以做到:

  • 约束
  • 索引视图
  • 触发
  • 不同的表和关系

<强>约束

在这种方法中,首先我们需要创建能够完成工作的功能。

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