Reputation:
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
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:
First approach(largest one) will involve creation of several database objects:
Nested table SQL type
create or replace type T_clasids is table of number;
Package which will have a variable of that SQL type to store class ID
s. 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;
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
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
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