尽管触发器的条件不满足,但仍触发了不需要的触发器

问题描述 投票:0回答:1

我正在为 SQL Server 中的触发器进行分配。详细的赋值问题是:

院长必须是所属院系的讲师,并拥有“VICEDOCTOR”或“DOCTOR”学位

我正在编写一个触发器集,总共包含 4 个子触发器,其中 3 种情况是不满足条件时的情况,最后 1 种情况是满足条件时的情况。

这是我的 SQL Server 代码:

USE master 
go

ALTER DATABASE [DepartmentManagement] 
    SET single_user WITH ROLLBACK IMMEDIATE

DROP DATABASE [DepartmentManagement]

CREATE DATABASE DepartmentManagement
GO

SET DATEFORMAT DMY
GO

USE DepartmentManagement

CREATE TABLE INSTRUCTOR
(
    INSTRUCTORID char(4) PRIMARY KEY,
    FULLNAME varchar(40),
    DEGREE varchar(10),
    INSTRUCTORRANK varchar(10),
    SEX varchar(10),
    DATEOFBIRTH smalldatetime, 
    STARTDAY smalldatetime,
    COEFFICIENT numeric(4,2),
    SALARY money,
    FACULTYID varchar(4)
)
GO

CREATE TABLE FACULTY
(
    FACULTYID varchar(4) PRIMARY KEY,
    FACULTYNAME varchar(40) NOT NULL,
    FOUNDINGDAY smalldatetime NOT NULL,
    FACULTYHEAD char(4) NOT NULL
)
GO

INSERT INTO INSTRUCTOR 
VALUES ('GV01', 'Andy White', 'VICEDOCTOR', 'PROFESSOR', 'MALE', '2/5/1950', '11/1/2004', '5', '2,250,000', 'CS')

INSERT INTO INSTRUCTOR
VALUES ('GV05', 'Walter Brock', 'DOCTOR', 'PROFESSOR', 'MALE', '12/3/1958', '12/1/2005', '3', '1,350,000', 'IS')
GO

CREATE TRIGGER trg_ins_facultyhead 
ON FACULTY
AFTER INSERT
AS
BEGIN
    IF EXISTS (SELECT * 
               FROM INSERTED, INSTRUCTOR    -- case #1: correct FACULTY but not DEGREE
               WHERE INSERTED.FACULTYHEAD = INSTRUCTOR.INSTRUCTORID
                 AND INSERTED.FACULTYID = INSTRUCTOR.FACULTYID
                 AND DEGREE != 'VICEDOCTOR' 
                 AND DEGREE != 'DOCTOR')
    BEGIN
        -- ERROR --
        RAISERROR('ERROR: FACULTY HEAD NEEDS TO HAVE DEGREE AS EITHER ''VICE DOCTOR'' OR ''DOCTOR'' ', 16, 1)
        -- RESTORE TO PREVIOUS STAGE---
        ROLLBACK TRANSACTION
    END

    IF EXISTS (SELECT * 
               FROM INSERTED, INSTRUCTOR        -- Case #2: both degree and faculty do not satisfy
               WHERE INSERTED.FACULTYHEAD = INSTRUCTOR.INSTRUCTORID
                 AND INSERTED.FACULTYID != INSTRUCTOR.FACULTYID
                 AND DEGREE != 'VICEDOCTOR' 
                 AND DEGREE != 'DOCTOR')
    BEGIN
        -- ERROR --
        RAISERROR('ERROR: FACULTY HEAD NEEDS TO BELONG TO CORRESPONDING FACULTY AND DEGREE AS EITHER ''VICE DOCTOR'' OR ''DOCTOR'' ', 16, 1)
        -- RESTORE TO PREVIOUS STAGE --
        ROLLBACK TRANSACTION
    END

    IF EXISTS (SELECT * 
               FROM INSERTED, INSTRUCTOR        -- Case #3: correct DEGREE but not FACULTY
               WHERE INSERTED.FACULTYHEAD = INSTRUCTOR.INSTRUCTORID
                 AND INSERTED.FACULTYID != INSTRUCTOR.FACULTYID
                 AND DEGREE = 'VICEDOCTOR' OR DEGREE = 'DOCTOR')
    BEGIN
        -- ERROR --
        RAISERROR('ERROR: FACULTY HEAD NEEDS TO BELONG TO CORRESPONDING FACULTY ', 16, 1)
        -- RESTORE TO PREVIOUS STAGE --
        ROLLBACK TRANSACTION
    END

    IF EXISTS (SELECT * 
               FROM INSERTED, INSTRUCTOR        -- Case #4: both FACULTY and DEGREE satisfy
               WHERE INSERTED.FACULTYHEAD = INSTRUCTOR.INSTRUCTORID
                 AND INSERTED.FACULTYID = INSTRUCTOR.FACULTYID
                 AND DEGREE = 'VICEDOCTOR' OR DEGREE = 'DOCTOR')
    BEGIN
        -- SUCCESS --
        PRINT 'ADDED FACULTY HEAD SUCCESSFULLY!'
    END
END
GO

INSERT INTO FACULTY 
VALUES ('CS', 'Computer Science', '7/6/2005', 'GV01')
GO

SELECT * 
FROM FACULTY, INSTRUCTOR

问题从第3种情况开始。当我执行

insert into FACULTY values ('CS','Computer Science','7/6/2005','GV01') GO
时,尽管条件根本不匹配,但第三种情况触发器被触发。我预计这是最后一个案例,插入成功,没有任何错误。

然后我删除

INSERT INTO INSTRUCTOR
VALUES ('GV05', 'Walter Brock', 'DOCTOR', 'PROFESSOR', 'MALE', '12/3/1958', '12/1/2005', '3', '1,350,000', 'IS')

它按我的预期工作。

然后我尝试更换

INSERT INTO FACULTY
VALUES ('CS', 'Computer Science', '7/6/2005', 'GV01')
GO

INSERT INTO FACULTY 
VALUES ('IS', 'Information System', '7/6/2005', 'GV05')

然后删除

INSERT INTO INSTRUCTOR
VALUES ('GV05', 'Walter Brock', 'DOCTOR', 'PROFESSOR', 'MALE', '12/3/1958', '12/1/2005', '3', '1,350,000', 'IS')

并且它可以在没有触发错误情况触发器的情况下工作,然后我检查结果表,虽然 ID 条件不匹配,但插入的表和讲师表似乎已连接。这会弄乱连接的表并导致触发不需要的触发器。

那么谁能解释一下,尽管 条件

WHERE INSERTED.FACULTYHEAD = INSTRUCTOR.INSTRUCTORID
不满足?

如何解决这个连接表问题并使触发器正确运行?

提前感谢您的帮助!

sql-server t-sql triggers
1个回答
0
投票

您的主要问题是

OR
,它的优先级低于
AND
。您需要将
AND (DEGREE ='VICEDOCTOR' OR DEGREE = 'DOCTOR')
括在括号中,或使用
IN
,如
AND DEGREE IN ('VICEDOCTOR', 'DOCTOR')

但是你把这个问题完全复杂化了。您不需要四次单独的检查,只需要两次。无论哪个错误先抛出都会给出错误。

进一步说明:

  • 您需要考虑到
    DEGREE
    为空。
  • 您也需要在
    UPDATE
    上强制执行此操作。
  • 使用
    SET NOCOUNT ON
    防止虚假的客户端结果集并提高性能。
  • 使用
    THROW
    而不是
    RAISERROR
    ROLLBACK
    ,因为这会正确抛出错误,而不是给客户端一个奇怪的回滚错误。
  • 使用显式连接语法,因为它更清晰。
  • FACULTYHEAD
    需要可为空,否则你根本无法创建
    FACULTY
CREATE TRIGGER trg_facultyhead ON FACULTY
AFTER INSERT, UPDATE
AS

SET NOCOUNT ON;

IF EXISTS( SELECT 1
        FROM INSERTED i
        JOIN INSTRUCTOR ins ON i.FACULTYHEAD = ins.INSTRUCTORID
        WHERE (ins.DEGREE NOT IN ('VICEDOCTOR', 'DOCTOR') OR ins.DEGREE IS NULL)
    )
        THROW 50001, N'ERROR: FACULTY HEAD NEEDS TO HAVE DEGREE AS EITHER ''VICE DOCTOR'' OR ''DOCTOR'' ', 1;
    ---  THROW handles rollback


IF EXISTS( SELECT 1
       FROM INSERTED i
       JOIN INSTRUCTOR ins ON i.FACULTYHEAD = ins.INSTRUCTORID
       WHERE i.FACULTYID != ins.FACULTYID
    )
        THROW 50002, N'ERROR: FACULTY HEAD NEEDS TO BELONG TO CORRESPONDING FACULTY', 1;
    -- THROW handles rollback

GO

话虽如此,我实际上会通过约束而不是触发器来强制执行此操作。

CREATE TABLE INSTRUCTOR
(
    INSTRUCTORID char(4) primary key,
    FULLNAME varchar(40),
    DEGREE varchar(10),
    INSTRUCTORRANK varchar(10),
    SEX varchar(10),
    DATEOFBIRTH smalldatetime, 
    STARTDAY smalldatetime,
    COEFFICIENT numeric(4,2),
    SALARY money,
    FACULTYID varchar(4) NOT NULL REFERENCES FACULTY (FACULTYID),
    INDEX IX_FACULTY UNIQUE (INSTRUCTORID, FACULTY)
);

CREATE TABLE FACULTY
(
    FACULTYID varchar(4) primary key,
    FACULTYNAME varchar(40) NOT NULL,
    FOUNDINGDAY smalldatetime NOT NULL,
    FACULTYHEAD char(4),
    FOREIGN KEY (FACULTYHEAD, FACULTYID) REFERENCES (INSTRUCTORID, FACULTY)
);

注意在

INSTRUCTOR
上添加了唯一索引/约束,这允许我们从
FACULTY
向其放置复合 FK,从而强制执行第一个条件。

对于第二个条件,我们可以利用 SQL Server 中一个鲜为人知的技巧来进行多表约束。如果有必要,我们也可以将其用作第一个条件。

创建一个两行的虚拟表。

CREATE TABLE DummyTwoRows (dummy int NOT NULL);

INSERT DummyTwoRows VALUES (1),(1);

创建一个视图来表示我们想要存在的行。

CREATE VIEW dbo.vFacultyHeadMustBeDoctors
WITH SCHEMABINDING
AS
SELECT
  f.FACULTYHEAD,
  i.INSTRUCTORID,
  d.i
FROM dbo.FACULTY f
JOIN dbo.INSTRUCTOR i ON f.FACULTYHEAD = i.INSTRUCTORID
CROSS JOIN dbo.DummyTwoRows d
WHERE (i.DEGREE NOT IN ('VICEDOCTOR', 'DOCTOR') OR i.DEGREE IS NULL);

然后将其索引为物化视图。请注意,根据定义,此视图始终为空。任何尝试

CREATE UNIQUE CLUSTERED INDEX CX ON vFacultyHeadMustBeDoctors (i);
© www.soinside.com 2019 - 2024. All rights reserved.