【问题标题】:SQL Server bit column constraint, 1 row = 1, all others 0SQL Server 位列约束,1 行 = 1,所有其他 0
【发布时间】:2011-01-26 22:09:21
【问题描述】:

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

我该如何执行?

【问题讨论】:

  • 您考虑过替代架构吗?比如说,在其他地方有一个指向“默认”行的引用。
  • 请问什么版本的 SQL Server?
  • 真是个好问题!我写这篇文章只是为了表达我的其他 +99 点赞赏。
  • 我会假设,对于大多数情况,您希望保证“默认”行的存在。在我看来,您应该问如何强制恰好一行数据将位列设置为 1,而不是询问如何最多一行位列设置为 1 的数据。

标签: sql-server


【解决方案1】:

所有版本:

  • 触发器
  • 索引视图
  • 存储过程(例如写入时测试)

SQL Server 2008:a filtered index

CREATE UNIQUE INDEX IX_foo ON bar (MyBitCol) WHERE MyBitCol = 1

【讨论】:

【解决方案2】:

假设您的 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)

【讨论】:

  • +1 从未想过这一点。没有过滤索引的 DRI 解决方案
  • 在我看来,这是最好的解决方案,因为它不涉及触发器。事实上,这个解决方案中提供的所有内容都可能在原始表定义中内联实现。
【解决方案3】:

如果您想在插入/更新新记录时将旧的默认记录更改为 0,并且如果您想确保一条记录始终具有该值(即,如果您删除记录,则触发器是最好的主意)使用您将其分配给不同记录的值)。你必须决定这样做的规则。这些触发器可能很棘手,因为您必须考虑插入和删除表中的多条记录。那么如果一个批次有 3 条记录尝试更新成为默认记录,那么哪一条会胜出呢?

如果您想确保一个默认记录在其他人尝试更改它时永远不会更改,过滤索引是一个好主意。

【讨论】:

    【解决方案4】:

    您可以应用一个代替插入触发器并检查传入的值。

    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
    

    或者,您可以对列应用唯一约束。

    【讨论】:

      【解决方案5】:

      以下问题的公认答案既有趣又相关:

      Constraint for only one record marked as default

      “但是严肃的关系人会告诉你这些信息 应该只是在另一个表中。”

      有一个单独的 1 行表,告诉您哪条记录是“默认”的。 Anon 在他的评论中提到了这一点。

      我认为这是最好的方法 - 简单、干净且不需要容易出错或以后误解的“聪明”深奥解决方案。您甚至可以删除IsDefualt 列。

      【讨论】:

        【解决方案6】:

        这里可以采取不同的方法,但我认为只有两种是正确的。但是让我们一步一步来。

        我们有 Hierachy 表,其中有 Root 列。这一列告诉我们哪一行是当前的起点。如前所述,我们希望只有一个起点。

        我们认为我们可以做到:

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

        约束

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

        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 不为空的地方添加 - Root 必须有值,我们只能在 Root!=0 的地方添加 - 以及不同的组合

        CREATE UNIQUE INDEX ix_OnyOneRoot ON [gt].[Hierarchy](Root) WHERE Root !=0 and Root is not null
        

        这种方法也不是完美的。最多一个 Root 将被强制,但最小不会。要更新数据,我们需要将之前的行设置为 null 或 0。

        触发器

        我们可以做两种不同的触发器 - 防止触发器 - 这不会让我们输入错误的数据 - DoTheJob 触发器 - 在后台会为我们更新数据

        防止触发

        这与约束基本相同,如果我们只想强制一个根而不是我们无法更新或插入。

        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 毫秒,经过的时间 = 0 毫秒。

        加入:

        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 毫秒,经过的时间 = 0 毫秒。

        总结 我将使用触发器。它是表中的一些魔法,但它在引擎盖下完成了所有工作。

        轻松创建表:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-09-04
          • 1970-01-01
          相关资源
          最近更新 更多