在我的
University
数据库中,我有 Student
、Takes
和 Course
表:
Student
(ID、姓名、部门名称、总学分)Takes
(学生 ID、课程 ID、部门 ID、学期、年份、年级)Course
(课程 ID、标题、部门名称、学分)我想编写一个触发器来自动更新获得可接受成绩的学生的
TotalCredits
(当教师更新学生所修课程的成绩时)。
我编写了这个触发器并执行了它:
CREATE TRIGGER [dbo].[credits_earned]
ON [dbo].[Takes]
AFTER UPDATE
AS
BEGIN
IF UPDATE(grade)
BEGIN
DECLARE @new_grade varchar(2)
SELECT @new_grade = I.grade
FROM inserted I
DECLARE @old_grade varchar(2)
SELECT @old_grade = T.grade
FROM Takes T
IF @new_grade<>'F' AND @new_grade IS NOT NULL
AND (@old_grade = 'F' OR @old_grade IS NULL)
BEGIN
UPDATE Student
SET tot_cred = tot_cred +
(SELECT credits
FROM Course C
WHERE C.course_id = (SELECT I.course_id
FROM inserted I))
WHERE Student.id = (SELECT I.id FROM inserted I)
END
END
END
命令成功完成。
但是当我执行一个程序来更新学生课程的成绩时(在 Takes 表中一年级为空),触发器对该学生的
TotalCredits
没有影响。
我将不胜感激你写的任何答案。
这种类型的触发器是一个非常糟糕的主意,应该尽可能避免,因为它很容易出现问题。如果变更检测逻辑出错,您就会得到错误的数据。如果触发器出现错误,您会得到不同步的数据。理想情况下,您总是在需要时计算这些信息,例如使用视图,而不是存储计算值。
但是,如果您有使用触发器,那么在编写触发器逻辑时,它仍然是SQL,因此仍然应该基于集合而不是基于过程(因为SQL Server针对基于集合的操作进行了优化)。
现在我认为以下方法有效。 (我正在假设什么唯一标识
Take
,以便将 Inserted
连接到 Deleted
(这就是检测更改的方式)。
然后我们需要预先聚合,因为无论成绩是否改变,您的聚合都会为所有
Takes
添加学分。
另请注意,我倾向于反转
UPDATE
检查,以便明确其是提前退出选项。
IF NOT UPDATE(Grade) RETURN;
UPDATE s SET
tot_cred = tot_cred + c.Credits
FROM Student s
JOIN (
SELECT i.StudentId, SUM(c.Credits) Credits
FROM Inserted i
JOIN Deleted d ON d.StudentId = i.StudentId
AND d.CourseId = i.CourseId
AND d.SectionId = i.SectionId
AND d.Semester = i.Semester
AND d.Year = i.Year
JOIN Course c ON c.CourseId = i.CourseId
WHERE i.Grade <> 'F' AND i.Grade IS NOT NULL
AND (d.Grade = 'F' OR d.Grade IS NULL)
GROUP BY i.StudentId
) c ON c.Student = s.Id;
此外,让触发器更新增量值是风险更大的方法,因为如果总数不正确,那么它将保持不正确。通常最好每次都从头开始计算总数,例如:
UPDATE s SET
tot_cred = (
-- Whatever logic gives the accurate credits at a point in time
SELECT SUM(c.Credits)
FROM Takes t
JOIN Courses c on c.CourseId = t.CourseId
WHERE t.StudentId = s.Id
AND t.Grade <> 'F'
)
FROM Student s
WHERE s.id IN (
SELECT i.StudentId
FROM Inserted i
JOIN Deleted d ON d.StudentId = i.StudentId
AND d.CourseId = i.CourseId
AND d.SectionId = i.SectionId
AND d.Semester = i.Semester
AND d.Year = i.Year
WHERE i.Grade <> 'F' AND i.Grade IS NOT NULL
AND (d.Grade = 'F' OR d.Grade IS NULL)
)
这可能可以进一步简化如下 - 这可能会更新那些没有任何改变的奇怪学生 - 但这是为了简单性和速度而付出的一个很小的代价:
UPDATE s SET
tot_cred = (
-- Whatever logic gives the accurate credits at a point in time
SELECT SUM(c.Credits)
FROM Takes t
JOIN Courses c on c.CourseId = t.CourseId
WHERE t.StudentId = s.Id
AND t.Grade <> 'F'
)
FROM Student s
WHERE s.id IN (
SELECT i.StudentId
FROM Inserted i
WHERE i.Grade <> 'F'
)