【问题标题】:SQL Trigger giving error. Subquery returned more than 1 valueSQL 触发器给出错误。子查询返回超过 1 个值
【发布时间】:2021-11-27 01:21:47
【问题描述】:

我有这个数据库,我创建了一个函数来更新学生的 GPA。

CREATE OR ALTER FUNCTION fGPA
    (@Mno varchar(9))
RETURNS DECIMAL(10,4)
BEGIN
    DECLARE @gpa decimal(10,4);

    SET @gpa = (SELECT 
                    (SUM(CASE e.Grade 
                             WHEN 'A' THEN 4.0
                             WHEN 'B' THEN 3.0
                             WHEN 'C' THEN 2.0
                             WHEN 'D' THEN 1.0
                             WHEN 'F' THEN 0.0 
                         END * c.Credit) / SUM(c.Credit)) 
                FROM Enroll AS e
                INNER JOIN Section AS s ON s.Sid = e.Sid
                INNER JOIN Course AS c ON c.Cno = s.Cno
                WHERE @Mno = e.Mno);
    
    RETURN @gpa;
END

我现在正在尝试创建一个触发器,以便每当在 Enroll 中更新成绩时,都会调用 fGPA 函数并同时更新 GPA。这是我所拥有的:

CREATE OR ALTER TRIGGER update_enroll
ON Enroll
AFTER UPDATE
AS
    IF (UPDATE(Grade))
    BEGIN
        DECLARE @updateGPA DECIMAL(10,4);

        SET @updateGPA = (SELECT dbo.fGPA(e.Mno) 
                          FROM Enroll as e
                          INNER JOIN inserted i ON i.Mno = e.Mno);

        UPDATE Student 
        SET student.GPA = @updateGPA;
    END

我得到的错误是:

子查询返回超过 1 个值。当子查询跟随 =、!=、、>= 或子查询用作表达式时,这是不允许的。

当我尝试执行此语句时会发生这种情况

UPDATE ENROLL 
SET Grade = 'B' 
WHERE Sid = '17504';

【问题讨论】:

  • 这可能是您在fGPA 函数中的WHERE 子句。看起来您可能已经交换了 @Mnoe.Mno
  • 如果表中有超过一行,则此查询将返回不止一行:SELECT dbo.fGPA(Mno) FROM Enroll。您需要加入inserted 虚拟表以识别更新的行并编写触发器来处理多行更新。
  • 好的,谢谢。 @DanGuzman 你能展示我如何编写触发器来处理多行更新吗?
  • @BeanBop,我在互联网上搜索“SQL 触发器给出错误。子查询返回超过 1 个值”,this answer 是列表中的第一个。这将回答你的问题。
  • 好的,谢谢。是的,我也在看这个。我已经更新了触发器以进行类似的更改,但我仍然遇到同样的错误。

标签: sql-server triggers


【解决方案1】:
…
BEGIN
/*
DECLARE @updateGPA DECIMAL(10,4);

SET @updateGPA = (SELECT dbo.fGPA(e.Mno) 
                  FROM Enroll as e
                  INNER JOIN inserted i ON i.Mno = e.Mno);
*/

UPDATE Student 
SET GPA = dbo.fGPA(Mno)
WHERE Mno IN (SELECT i.Mno FROM inserted AS i)

END
…

【讨论】:

    【解决方案2】:

    您正在制作a very common mistake with triggersinserted 表可能有多个(或零个)行。所以你应该把它作为一个联合更新来做。

    请注意,UPDATE() 函数仅告诉您该列是否存在于 UPDATE 语句中,而不是它是否已实际更新。您还应该加入 deleted 表以排除此类行。

    这导致了另一个常见问题:使用标量用户定义函数。它们很慢,通常应该避免。相反,请使用 内联 表值函数。

    你也在更新整个Student 表,你没有加入任何东西。

    CREATE OR ALTER FUNCTION fGPA
        (@Mno varchar(9))
    RETURNS TABLE
    AS RETURN
    
        SELECT 
            GPA = SUM(CASE e.Grade 
                     WHEN 'A' THEN 4.0
                     WHEN 'B' THEN 3.0
                     WHEN 'C' THEN 2.0
                     WHEN 'D' THEN 1.0
                     WHEN 'F' THEN 0.0 
                 END * c.Credit) / SUM(c.Credit)
        FROM Enroll AS e
        INNER JOIN Section AS s ON s.Sid = e.Sid
        INNER JOIN Course AS c ON c.Cno = s.Cno
        WHERE @Mno = e.Mno;
    

    那你就直接CROSS APPLY吧。

    CREATE OR ALTER TRIGGER update_enroll
    ON Enroll
    AFTER UPDATE
    AS
        SET NOCOUNT ON;
        IF (NOT UPDATE(Grade) OR NOT EXISTS (SELECT 1 FROM inserted))  -- bail out for no rows
            RETURN;
    
        UPDATE s
        SET s.GPA = gpa.GPA
        FROM inserted i
        JOIN Student s ON s.Mno = i.Mno
        CROSS APPLY dbo.fGPA(i.Mno) gpa;
    

    我会说我不认为触发器真的是解决这个问题的正确答案。

    理想情况下,您不应将相同信息的两份副本保存在两个地方。而是创建一个包含此计算的视图。

    CREATE VIEW vGPA
    AS
    
        SELECT
            s.Mno,
            GPA = SUM(CASE e.Grade 
                     WHEN 'A' THEN 4.0
                     WHEN 'B' THEN 3.0
                     WHEN 'C' THEN 2.0
                     WHEN 'D' THEN 1.0
                     WHEN 'F' THEN 0.0 
                 END * c.Credit) / SUM(c.Credit)
        FROM dbo.Student AS s
        INNER JOIN dbo.Enroll AS e ON e.Mno = s.Mno
        INNER JOIN dbo.Section AS s ON s.Sid = e.Sid
        INNER JOIN dbo.Course AS c ON c.Cno = s.Cno
        GROUP BY
            s.Mno;
    

    如果您担心性能,可以索引视图以支持这一点。服务器将确保索引始终与基表保持同步。

    很遗憾,您不能索引聚合上的计算表达式(即SUM / SUM)。相反,创建一个包含两个 SUMs 的基本视图,然后创建另一个从中选择值的标准视图。

    CREATE VIEW vGPA_indexed
    WITH SCHEMABINDING  -- needs schema binding
    AS
    
        SELECT
            s.Mno,
            SumGrade = SUM(CASE e.Grade 
                     WHEN 'A' THEN 4.0
                     WHEN 'B' THEN 3.0
                     WHEN 'C' THEN 2.0
                     WHEN 'D' THEN 1.0
                     WHEN 'F' THEN 0.0 
                 END * c.Credit),
            SumCredit = SUM(c.Credit),
            TotalRows = COUNT_BIG(*)  -- needs count in grouped view
        FROM dbo.Student AS s
        INNER JOIN dbo.Enroll AS e ON e.Mno = s.Mno
        INNER JOIN dbo.Section AS s ON s.Sid = e.Sid
        INNER JOIN dbo.Course AS c ON c.Cno = s.Cno
        GROUP BY
            s.Mno;
    
    CREATE UNIQUE CLUSTERED INDEX CX_vGPA_indexed on vGPA_indexed (Mno);
    
    CREATE VIEW dbo.vGPA
    AS
    
        SELECT
            s.Mno,
            GPA = SumGrade / SumCredit
        FROM dbo.vGPA_indexed WITH (NOEXPAND);  -- needs NOEXPAND in standard edition, and anyway better for performance
    

    【讨论】:

      猜你喜欢
      • 2022-01-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多