更新后触发以保持总学分最新

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

在我的

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-server t-sql triggers sql-update sql-server-2019
1个回答
2
投票

这种类型的触发器是一个非常糟糕的主意,应该尽可能避免,因为它很容易出现问题。如果变更检测逻辑出错,您就会得到错误的数据。如果触发器出现错误,您会得到不同步的数据。理想情况下,您总是在需要时计算这些信息,例如使用视图,而不是存储计算值。


但是,如果您使用触发器,那么在编写触发器逻辑时,它仍然是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'
    )
© www.soinside.com 2019 - 2024. All rights reserved.