【问题标题】:how to use trigger and cursor in combined with for loop in oracle?如何在oracle中将触发器和光标与for循环结合使用?
【发布时间】:2013-09-03 05:09:39
【问题描述】:

假设有两个表

 1.student(roll_no, class, credits)
 2.class(class, total_student, Total_credits)

如何使用学生表上的光标创建触发器,该触发器将更新班级学生总数他们的总学分? 在每次插入或删除时

【问题讨论】:

标签: sql oracle triggers


【解决方案1】:

更新class 表的total_studenttotal_credit 列将涉及在student 表上定义的触发器中编写针对student 表的查询。这样做会导致ORA-04091: table name is mutating, trigger/function may not see it 错误。为避免该错误,至少有三种方法可以在 student 表中每次发生更改(删除/插入/更新)时获取 total_studenttotal_credits 更新。假设表在master(class)/detail(student)关系中:

  1. 第一种方法(最大的)将涉及创建几个数据库对象:

    1. 嵌套表SQL类型

      create or replace type T_clasids is table of number;
      
    2. 将具有该 SQL 类型的变量来存储类 IDs 的包。以某种方式受到 DML 语句的影响。

      包装规格:

      create or replace package Pkg
      is
        procedure reset_list_of_ids;      -- empties out the list of class ids
        procedure add_id(p_id in number); -- add new class id to a list if row
                                          -- with this ID has been affected by  
                                          -- a DML statement
        procedure update_class;           -- updates class table
      end;
      

      包体:

      create or replace package body PKG
      is
      
         g_classids T_clasids := T_clasids();
      
      procedure reset_list_of_ids
      is
      begin
        g_classids.delete;
      end;
      
      procedure add_id(p_id in number)
      is
      begin
        g_classids.extend;
        g_classids(g_classids.count) := p_id;
      end;
      
      procedure update_class
      is
      begin
        update class t
           set (  t.total_student
                , t.total_credits ) = ( select count(*)
                                             , sum(s.credits)
                                          from student s
                                         where s.class = t.class)
         where t.class in (select column_value
                             from table(g_classids));
      
       end;
      

      结束;

    3. 三个触发器: a) Before 语句; b) 排后; c) 声明后。

      -- before insert/update/delete statement level trigger 
      -- to empty out the class id list
      create or replace trigger tr_bs_initialize
      before insert or delete or update on student
      begin
        pkg.reset_list_of_ids;
      end;
      
      
      
      -- after insert/update/delete statement level trigger
      -- to update class table with new information 
      create or replace trigger tr_as_update_class
      after insert or delete or update on student
      begin
        pkg.update_class;
      end;
      
      
      -- after insert/update/delete row level trigger
      -- to populate class id collection with ids of 
      -- rows which has been affected by a DML statement
      create or replace trigger tr_ar_populate
      after insert or delete or update on student
      for each row
      begin
         -- nvl(:new.class, :old.class)
         -- in a case :new.clas happens to be null
         pkg.add_id(nvl(:new.class, :old.class));
      end;
      

      这是一个如何工作的例子:

      select t.* from class t;
      
      CLASS      TOTAL_STUDENT TOTAL_CREDITS
      ---------- ------------- -------------
       1         null          null     
       2         null          null 
       3         null          null
      
       insert into student(roll_no, class, credits)
          values(1, 2, 3);
      
      
       select t.* from class t;
      
       CLASS      TOTAL_STUDENT TOTAL_CREDITS
       ---------- ------------- -------------
       1             null          null 
       2             1             3
       3             null          null
      
  2. 第二种方法(最短的一种,个人更喜欢的一种)是从class 表中删除total_studenttotal_credits,创建一个视图,该视图将计算并保持有关班级学生总数的最新信息和他们的学分总和:

        create or replace view v_class as
           select c.class
                , count(s.class) as total_students
                , sum(s.credits) as total_credits
             from student s
            right join class c
               on (c.class = s.class)
            group by c.class
    
         select t.* from v_class t;
    
         CLASS      TOTAL_STUDENTS TOTAL_CREDITS
         ---------- -------------  -------------
         1             null          null 
         2             1             3
         3             null          null
    
  3. 第三种方法。在sudent表上定义一个插入/更新/删除语句级触发器,并使用merge语句更新class表:

         create or replace trigger tr_aiudsl
         after insert or update or delete on student
         begin
            merge into class c
            using (select t.class
                        , count(*)      as total_students
                        , sum(t.credits)as total_credit
                     from student t
                    group by t.class) q
                       on (q.class = c.class)
             when matched
             then update
               set c.total_student = q.total_students
                 , c.total_credits  = q.total_credit;
         end;
    

    Find out more关于merge的声明。

【讨论】:

  • +一个这样详细的解释。不过我有一个疑问,方法三不会引发变异触发器,或者如果使用合并,可以避免变异触发器?
  • @Polppan 在第三种方法中,我们使用 after statement(not row) 级别的触发器。这种触发器不会引发 mutating table 错误 - 在它们已经修改时访问数据
  • 我们的老师更喜欢第三种方法,但我们只能使用光标,并且在此作业中不允许使用触发器视图。但感谢为我和我的朋友们提供了一个非常简短的描述:)
猜你喜欢
  • 1970-01-01
  • 2013-08-18
  • 2018-12-10
  • 1970-01-01
  • 2023-04-05
  • 1970-01-01
  • 2018-01-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多