我正在为 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
不满足?
如何解决这个连接表问题并使触发器正确运行?
提前感谢您的帮助!
您的主要问题是
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);