【问题标题】:Validating UPDATE and INSERT statements against an entire table针对整个表验证 UPDATE 和 INSERT 语句
【发布时间】:2009-06-18 13:48:53
【问题描述】:

我正在寻找向表添加约束的最佳方式,该约束实际上是记录与该表中其余记录之间关系的唯一索引。

想象下表描述了各种警卫的巡逻(来自之前的守望者场景)

PK  PatrolID Integer
FK  GuardID  Integer
    Starts   DateTime
    Ends     DateTime

我们从一个约束开始,指定开始和结束时间必须是合乎逻辑的:

Ends >= Starts

但是我想添加另一个逻辑约束:特定的警卫 (GuardID) 不能同时在两个地方,这意味着对于任何记录,由 Start/Ends 指定的时间段不应与为任何其他记录定义的时间段重叠由同一个警卫巡逻。

我可以想到两种方法来解决这个问题:

创建一个 INSTEAD OF INSERT 触发器。然后,此触发器将使用游标遍历 INSERTED 表,检查每条记录。如果任何记录与现有记录发生冲突,则会引发错误。这种方法的两个问题是:我不喜欢在现代版本的 SQL Server 中使用游标,而且我不确定如何为 UPDATE 实现相同的逻辑。 INSERTED 中记录的复杂性也可能相互冲突。

第二种似乎更好的方法是创建一个调用用户定义函数的 CONSTRAINT,传递 PatrolID、GuardID、Starts 和 Ends。然后,该函数将执行 WHERE EXISTS 查询,检查与 GuardID/Starts/Ends 参数重叠且不是原始 PatrolID 记录的任何记录。但是我不确定这种方法可能会产生什么潜在的副作用。

第二种方法更好吗?有没有人看到任何陷阱,例如一次插入/更新多行时(我担心的是因为该组中的行可能会发生冲突,这意味着它们“插入”的顺序会有所不同)。有没有更好的方法来做到这一点(比如一些花哨的 INDEX 技巧?)

【问题讨论】:

    标签: sql-server sql-server-2005 validation triggers constraints


    【解决方案1】:

    使用后触发器检查是否违反了重叠约束:

    create trigger Patrol_NoOverlap_AIU on Patrol for insert, update as
        begin
        if exists (select *
            from inserted i
            inner join Patrol p
                on i.GuardId = p.GuardId
                and i.PatrolId <> p.PatrolId
            where (i.Starts between p.starts and p.Ends)
            or (i.Ends between p.Starts and p.Ends))
    
            rollback transaction
        end
    

    注意:在触发器中回滚事务将终止批处理。与正常的约束违规不同,您将无法捕捉到错误。

    您可能需要不同的 where 子句,具体取决于您定义时间范围和重叠的方式。例如,如果您希望能够说 Guard #1 在 X 从 6:00 到 7:00,那么 Y 7:00 到 8:00 上述情况是不允许的。你会想要:

    create trigger Patrol_NoOverlap_AIU on Patrol for insert, update as
        begin
        if exists (select *
            from inserted i
            inner join Patrol p
                on i.GuardId = p.GuardId
                and i.PatrolId <> p.PatrolId
            where (p.Starts <= i.Starts and i.Starts < p.Ends)
            or (p.Starts <= i.Ends and i.Ends < p.Ends))
    
            rollback transaction
        end
    

    Starts 是保护开始的时间,Ends 是保护结束后的无穷小时刻。

    【讨论】:

      【解决方案2】:

      最简单的方法是使用存储过程进行插入。存储过程可以在单个语句中进行插入:

      insert into YourTable
      (GuardID, Starts, Ends)
      select @GuardID, @Starts, @Ends
      where not exists (
          select *
          from YourTable
          where GuardID = @GuardID
          and Starts <= @Ends
          and Ends >= @Start
      )
      
      if @@rowcount <> 1
          return -1 -- Failure
      

      根据我的经验,UDF 的触发器和约束往往会变得非常复杂。它们有副作用,可能需要大量调试才能弄清楚。

      存储过程可以正常工作,并且它们还有一个额外的优势,即您可以拒绝客户端的 INSERT 权限,从而让您可以对进入数据库的内容进行细粒度控制。

      【讨论】:

      • 这种类型的事情需要触发器,因为可以从 proc 以外的地方插入记录。如果您不在触发级别强制执行,则无法保证数据完整性。
      【解决方案3】:
      CREATE TRIGGER [dbo].[emaill] ON [dbo].[email]
      FOR INSERT
      
      AS
      
      BEGIN
      
      
          declare @email CHAR(50);
      
      
          SELECT @email=i.email from inserted i;
      
          IF @email NOT LIKE '%_@%_.__%' 
          BEGIN
                  print 'Triggered Fired';
                  Print 'Invalid Emaill....';
                  ROLLBACK TRANSACTION
          END 
      
      END
      

      【讨论】:

        【解决方案4】:
        猜你喜欢
        • 2023-03-31
        • 2016-01-22
        • 2021-03-27
        • 2019-04-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-19
        • 1970-01-01
        相关资源
        最近更新 更多