user2737208
user2737208

Reputation:

how to use trigger and cursor in combined with for loop in oracle?

assuming two table

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

how to create trigger using cursor on student table which will update class with Total no of Student's and their total credits ? on each insertion or deletion

Upvotes: 1

Views: 2757

Answers (1)

Nick Krasnov
Nick Krasnov

Reputation: 27251

Updating of total_student and total_credit columns of the class table will involve writing a query against student table in a trigger defined on student table. Doing so will lead to ORA-04091: table name is mutating, trigger/function may not see it error. To avoid that error there are at least three approaches to get the total_student and total_credits updated every time something is changing(delete/insert/update) in the student table. Assumption has been made that the table in the master(class)/detail(student) relationship:

  1. First approach(largest one) will involve creation of several database objects:

    1. Nested table SQL type

      create or replace type T_clasids is table of number;
      
    2. Package which will have a variable of that SQL type to store class IDs. that somehow has been affected by a DML statement.

      Package specification:

      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;
      

      Package body:

      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;
      

      end;

    3. Three triggers: a) Before statement; b) after row; c) after statement.

      -- 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;
      

      Here is an example of how it works:

      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. Second approach(shortest one and personally preferable one ) is to remove total_student and total_credits from the class table, create a view which will calculate and keep up to date information about total number of student in a class and sum of their 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. Third approach. Define one after insert/update/delete statement level trigger on the sudent table and use merge statement to update the class table:

         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 about merge statement.

Upvotes: 1

Related Questions