MSSQL:避免CHECK约束的Update语句

Ben*_*Ben 4 sql constraints sql-server-2000

在MS2000中工作,我有一个名为JobOwners的表,它将Jobs(JPSID)映射到拥有它们的Employees(EmpID).它还包含他们开始拥有该作业的日期(DateStarted),他们停止拥有该作业的日期(DateEnded)以及所有权是否处于活动状态(IsActive).看起来像这样.

CREATE TABLE JobOwners
(
    LogID int NOT NULL IDENTITY(1,1) PRIMARY KEY,
    JPSID int NOT NULL FOREIGN KEY REFERENCES JobsPerShift(JPSID),
    EmpID int NOT NULL FOREIGN KEY REFERENCES Employees(EmpID),
    DateStarted datetime,
    DateEnded datetime,
    IsActive tinyint NOT NULL   
)
Run Code Online (Sandbox Code Playgroud)

应该没有活动的JPSID重复项,尽管非活动重复项应该没问题.通过一些研究,我发现我可以使用CHECK约束上的函数来完成此操作.

CREATE FUNCTION CheckActiveCount(@JPSID INT) 
RETURNS INT AS 
BEGIN
    DECLARE @result INT
    SELECT @result = COUNT(*) FROM JobOwners WHERE JPSID = @JPSID AND IsActive = 1
    RETURN @result
END
GO

ALTER TABLE JobOwners 
 ADD CONSTRAINT CK_JobOwners_IsActive
 CHECK ((IsActive = 1 AND dbo.CheckActiveCount(JPSID) <= 1) OR (IsActive = 0))
Run Code Online (Sandbox Code Playgroud)

这很好用.它将允许我插入带有IsActive 1的JPSID 2,因为没有其他活动的JPSID 2.它将允许我插入带有IsActive 0的JPSID 2,因为当IsActive为0时不应用检查.当我尝试时它拒绝但是,再次使用IsActive 1插入JPSID 2,因为它与约束冲突.见下文.

INSERT INTO JobOwners
 VALUES(2,2,NULL,NULL,1)

(1 row(s) affected)

INSERT INTO JobOwners
 VALUES(2,2,NULL,NULL,0)

(1 row(s) affected)

INSERT INTO JobOwners
 VALUES(2,3,NULL,NULL,1)

INSERT statement conflicted with COLUMN FOREIGN KEY constraint...
Run Code Online (Sandbox Code Playgroud)

如果我尝试将其中一个非活动记录更新为活动,则会出现此问题.出于某种原因,它允许我.

UPDATE JobOwners SET IsActive = 1
 WHERE LogID = 3

(1 row(s) affected)
Run Code Online (Sandbox Code Playgroud)

如果我再次运行相同的语句,那么它与约束冲突,但不是第一次.这个应用程序的前端永远不会将非活动记录更改为活动状态,它只会插入一个新记录,但它仍然不是我希望表允许的内容.

我想知道是否最好将活跃的工作所有者分开,并为工作所有者历史提供一个单独的表格,但我不确定这里的最佳做法.任何帮助将不胜感激.

谢谢你,

Aar*_*and 7

存在一个已知问题,其中某些操作将导致检查约束,该约束调用绕过UDF.该错误列在Connect上(在它被破坏之前,所有链接都是孤立的)并且已经被确认,但是因为无法修复而关闭.这意味着我们需要依赖于变通方法.

我的第一个解决方法可能是而不是更新触发器.感谢Martin让我保持诚实并让我进一步测试 - 我发现我没有防止在同一个声明中将两行更新为1.我已经纠正了逻辑并添加了一个事务来帮助防止竞争条件:

CREATE TRIGGER dbo.CheckJobOwners ON dbo.JobOwners
INSTEAD OF UPDATE
AS
BEGIN
  SET NOCOUNT ON;
  BEGIN TRANSACTION;

  UPDATE j SET IsActive = 1 -- /* , other columns */
    FROM dbo.JobOwners AS j INNER JOIN inserted AS i
    ON i.LogID = j.LogID
    WHERE i.IsActive = 1 AND NOT EXISTS 
    (    -- since only one can be active, we don't need an expensive count:
      SELECT 1 FROM dbo.JobOwners AS j2
        WHERE j2.JPSID = i.JPSID
        AND j2.IsActive = 1 AND j2.LogID <> i.LogID
    )
    AND NOT EXISTS 
    (    -- also need to protect against two rows updated by same statement: 
      SELECT 1 FROM inserted AS i2
        WHERE i2.JPSID = i.JPSID
        AND i2.IsActive = 1 AND i2.LogID <> i.LogID
    );

  -- *if* you want to report errors:
  IF (@@ROWCOUNT <> (SELECT COUNT(*) FROM inserted WHERE IsActive = 1))
    RAISERROR('At least one row was not updated.', 11, 1);

  -- assume setting active = 0 always ok & that IsActive is not nullable
  UPDATE j SET IsActive = 0 -- /* , other columns */
    FROM dbo.JobOwners AS j INNER JOIN inserted AS i
    ON j.LogID = i.LogID
    WHERE i.IsActive = 0;

  COMMIT TRANSACTION;
END
GO
Run Code Online (Sandbox Code Playgroud)

(我的唯一原因是代替而不是触发后是你只更新你需要更新的行,而不是必须在事后回滚(这不会让你只在多个情况下回滚无效更新) - 更新)).

这里有很多关于这个问题的讨论:

https://web.archive.org/web/20171013131650/http://sqlblog.com/blogs/tibor_karaszi/archive/2009/12/17/be-careful-with-constraints-calling-udfs.aspx