как использовать триггер и курсор в сочетании с циклом for в оракуле?

предполагая две таблицы

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

как создать триггер с помощью курсора в таблице учеников, который будет обновлять класс с помощью общего количества учеников и их общего количества кредитов? при каждой вставке или удалении


person Community    schedule 02.09.2013    source источник
comment
Есть ли особая причина, по которой вы хотели бы использовать триггер, чтобы получить общее количество студентов и общее количество кредитов?   -  person Jacob    schedule 02.09.2013
comment
Вы пробовали его создать? Если да, то что не сработало?   -  person DCookie    schedule 02.09.2013
comment
Взгляните на это, может быть полезно. stackoverflow.com/questions/11580177/cursor-in-a-trigger   -  person Jacob    schedule 02.09.2013


Ответы (1)


Обновление столбцов total_student и total_credit таблицы class потребует написания запроса к таблице student в триггере, определенном для таблицы student. Это приведет к ошибке ORA-04091: имя таблицы изменяется, триггер/функция может ее не видеть. Чтобы избежать этой ошибки, существует как минимум три подхода к обновлению total_student и total_credits каждый раз, когда что-то изменяется (удаляется/вставляется/обновляется) в таблице student. Было сделано предположение, что таблица в отношении 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. Три триггера: а) Перед оператором; б) после ряда; в) после заявления.

      -- 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. Второй подход (самый короткий и лично предпочтительный) состоит в том, чтобы удалить total_student и total_credits из таблицы class, создать представление, которое будет вычислять и обновлять информацию об общем количестве студентов в классе и сумме их кредитов:

        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;
    

    Подробнее об операторе merge.

person Nick Krasnov    schedule 02.09.2013
comment
+ Один за такое подробное объяснение. Однако у меня есть одно сомнение: третий подход не вызывает мутирующий триггер или, если используется слияние, можно избежать мутирующего триггера? - person Jacob; 03.09.2013
comment
@Polppan В третьем подходе мы используем триггер уровня после оператора (не строки). Такие триггеры не вызывают ошибку mutating table — доступ к данным осуществляется к моменту их изменения. - person Nick Krasnov; 03.09.2013
comment
Наши учителя предпочитают 3-й подход, но мы должны использовать только курсор, а просмотр триггера в этом задании не разрешен. но спасибо, это действительно краткое описание для меня и моих друзей :) - person ; 04.09.2013