Mutating trigger in Oracle - sql

I create the following tables:
create table lessons(
id number,
name_teacher varchar2(9),
name_student varchar2(40),
start_lesson date,
end_lesson date
);
I inserted the following datas:
insert into lessons values (001,'Peter','Thomas',to_date('2015-12-15','YYYY-MM-DD'),to_date('2015-12-22','YYYY-MM-DD'));
insert into lessons values (002,'Eli','Alice',to_date('2015-06-16','YYYY-MM-DD'),to_date('2015-06-23','YYYY-MM-DD'));
insert into lessons values (003,'Daniel','Thomas',to_date('2015-08-15','YYYY-MM-DD'),to_date('2015-08-20','YYYY-MM-DD'));
Data that you cant add by the trigger.
insert into lessons values (001,'Peter','Alice',to_date('2015-12-16','YYYY-MM-DD'),to_date('2015-12-25','YYYY-MM-DD'));
insert into lessons values (002,'Eli','Thomas',to_date('2015-06-13','YYYY-MM-DD'),to_date('2015-06-20','YYYY-MM-DD'));
The question is how to make a trigger that does not allow me to add students who have teachers who overlap in time, like "Peter" or "Eli".
--- My problem ---
Oracle returns me an error of mutants tables.

you neen an after insert / update Trigger that fire after the comlete insert or update and not after earch row:
create or replace trigger check_intersections_trg
on
lessons
after insert or update
declare
v_res NUMBER;
begin
select count(*)
into v_res
from lessons l1
join lessons l2 on l1.name_student = l2.name_student
and l1.start_lesson <= l2.end_lesson
and l2.start_lesson <= l1.end_lesson
;
if v_res > 0 than
raise_application_error( -20999, 'intersection found');
end if;
end;
/

Related

How to get total no of rows affected by DML Statements in a DBMS Session of PLSQL block(Without using SQL%ROWCOUNT)? [duplicate]

I have a scenario where there may exist multiple DML statements inside my PLSQL Block, I was looking for some generic approach by using which I can calculate total no of rows affected using this block of code.
Test Data and Structure for reference:
create table cust_temp_a
(Name varchar2(100), id varchar2(100));
insert into cust_temp_a VALUES
('Hasu','10');
insert into cust_temp_a VALUES
('Aasu','20');
insert into cust_temp_a VALUES
('Basu','30');
insert into cust_temp_a VALUES
('Casu','10');
commit;
create table cust_temp_b
(Name varchar2(100), id varchar2(100));
insert into cust_temp_b VALUES
('Hasu','10');
insert into cust_temp_b VALUES
('Aasu','20');
insert into cust_temp_b VALUES
('Basu','30');
insert into cust_temp_b VALUES
('Casu','20');
commit;
There may exist multiple tables like this,
Below is the PLSQL Block with the capability of logging no of rows affected:
DECLARE
affected_count_a number;
affected_count_b number;
total_affected_count number;
PROCEDURE proc(affected_count_a OUT number,affected_count_b OUT number) IS
BEGIN
update cust_temp_a set name = 'new_val' where id = 10;
affected_count_a:=sql%rowcount;
update cust_temp_b set name = 'new_val' where id = 20;
affected_count_b:=sql%rowcount;
END;
BEGIN
proc(affected_count_a,affected_count_b);
total_affected_count:=affected_count_a+affected_count_b;
dbms_output.put_line('total_affected_count : ' || total_affected_count );
dbms_output.put_line('affected_count_a : ' || affected_count_a);
dbms_output.put_line('affected_count_b : ' || affected_count_b );
END;
/
commit;
Result :
total_affected_count : 4
affected_count_a : 2
affected_count_b : 2
There may exist multiple DML statements inside the procedure "proc", and I wanted to perform some generic approach to log individual count of each DML statement and at last aggregate, count affected by the "proc".
Adding DML Statement every time and adding the corresponding variable to log count is the pain.
You may log the counts in a generic logging table using a generic procedure.
Logging table
CREATE TABLE dml_logs (
log_id NUMBER PRIMARY KEY,
step VARCHAR2(200),
row_count NUMBER,
log_date DATE
);
Sequence for id
create sequence seq_dml_logs ;
Logging procedure
CREATE OR REPLACE PROCEDURE log_dml (
p_step VARCHAR2,
p_row_count NUMBER,
p_log_date DATE
) IS
PRAGMA autonomous_transaction;
BEGIN
INSERT INTO dml_logs (
log_id,
step,
row_count,
log_date
) VALUES (
seq_dml_logs.NEXTVAL,
p_step,
p_row_count,
p_log_date
);
COMMIT;
END;
/
PL/SQL block with DML
DECLARE
v_step dml_logs.step%TYPE;
BEGIN
v_step := 'cust_temp_a_update';
UPDATE cust_temp_a SET name = 'new_val' WHERE id = 10;
log_dml(v_step,SQL%ROWCOUNT,SYSDATE);
v_step := 'cust_temp_b_update';
UPDATE cust_temp_b SET name = 'new_val' WHERE id = 20;
log_dml(v_step,SQL%ROWCOUNT,SYSDATE);
END;
/
Then, aggregation is simple.
select SUM(row_count) FROM dml_logs
where step = ? and log_date = ? -- all the required conditions.
In order to better identify that the records belong to a particular run or a batch, you may add another column in the dml_logs called batch_number . Log this number to identify unique runs of your dmls and your query to get the aggregate details become much simpler.

How can I calculate aggregate affected rows if there are Multiple DML query in my PLSQL block?

I have a scenario where there may exist multiple DML statements inside my PLSQL Block, I was looking for some generic approach by using which I can calculate total no of rows affected using this block of code.
Test Data and Structure for reference:
create table cust_temp_a
(Name varchar2(100), id varchar2(100));
insert into cust_temp_a VALUES
('Hasu','10');
insert into cust_temp_a VALUES
('Aasu','20');
insert into cust_temp_a VALUES
('Basu','30');
insert into cust_temp_a VALUES
('Casu','10');
commit;
create table cust_temp_b
(Name varchar2(100), id varchar2(100));
insert into cust_temp_b VALUES
('Hasu','10');
insert into cust_temp_b VALUES
('Aasu','20');
insert into cust_temp_b VALUES
('Basu','30');
insert into cust_temp_b VALUES
('Casu','20');
commit;
There may exist multiple tables like this,
Below is the PLSQL Block with the capability of logging no of rows affected:
DECLARE
affected_count_a number;
affected_count_b number;
total_affected_count number;
PROCEDURE proc(affected_count_a OUT number,affected_count_b OUT number) IS
BEGIN
update cust_temp_a set name = 'new_val' where id = 10;
affected_count_a:=sql%rowcount;
update cust_temp_b set name = 'new_val' where id = 20;
affected_count_b:=sql%rowcount;
END;
BEGIN
proc(affected_count_a,affected_count_b);
total_affected_count:=affected_count_a+affected_count_b;
dbms_output.put_line('total_affected_count : ' || total_affected_count );
dbms_output.put_line('affected_count_a : ' || affected_count_a);
dbms_output.put_line('affected_count_b : ' || affected_count_b );
END;
/
commit;
Result :
total_affected_count : 4
affected_count_a : 2
affected_count_b : 2
There may exist multiple DML statements inside the procedure "proc", and I wanted to perform some generic approach to log individual count of each DML statement and at last aggregate, count affected by the "proc".
Adding DML Statement every time and adding the corresponding variable to log count is the pain.
You may log the counts in a generic logging table using a generic procedure.
Logging table
CREATE TABLE dml_logs (
log_id NUMBER PRIMARY KEY,
step VARCHAR2(200),
row_count NUMBER,
log_date DATE
);
Sequence for id
create sequence seq_dml_logs ;
Logging procedure
CREATE OR REPLACE PROCEDURE log_dml (
p_step VARCHAR2,
p_row_count NUMBER,
p_log_date DATE
) IS
PRAGMA autonomous_transaction;
BEGIN
INSERT INTO dml_logs (
log_id,
step,
row_count,
log_date
) VALUES (
seq_dml_logs.NEXTVAL,
p_step,
p_row_count,
p_log_date
);
COMMIT;
END;
/
PL/SQL block with DML
DECLARE
v_step dml_logs.step%TYPE;
BEGIN
v_step := 'cust_temp_a_update';
UPDATE cust_temp_a SET name = 'new_val' WHERE id = 10;
log_dml(v_step,SQL%ROWCOUNT,SYSDATE);
v_step := 'cust_temp_b_update';
UPDATE cust_temp_b SET name = 'new_val' WHERE id = 20;
log_dml(v_step,SQL%ROWCOUNT,SYSDATE);
END;
/
Then, aggregation is simple.
select SUM(row_count) FROM dml_logs
where step = ? and log_date = ? -- all the required conditions.
In order to better identify that the records belong to a particular run or a batch, you may add another column in the dml_logs called batch_number . Log this number to identify unique runs of your dmls and your query to get the aggregate details become much simpler.

Creating Oracle SQL Trigger Error

This is what I need to accomplish: Create a TRIGGER named tgr_customer_insert that will fire AFTER a row is inserted into the customers table.
The trigger can be created after you create the cardholder table, so it can be in the same ps16a.sql file just created. This trigger will insert a row into the cardholder table when a row is inserted into the temp_table_customers table. Here are the columns to insert:
card_number (this is inserted using the seq_cardholder sequence number)
customer_id (this is a bind variable from the temp_table_customer table using the :new.column_name syntax)
credit_limit (this is a bind variable from the temp_table_customer table using the :new.column_name syntax)
This is my code:
`CREATE OR REPLACE TRIGGER tgr_customer_insert
AFTER INSERT
ON customers
FOR EACH ROW
BEGIN
-- Insert record into customers table
INSERT INTO cardholder
( card_number,
customer_id,
credit_limit
)
VALUES
( new.seq_cardholder,
:new.customer_id,
:new.credit_limit
);
END;
`
Error is: ORA-24344: success with compilation error
Line 3 Position 4.
Hair is being torn out. Thank you in advance for you time with this matter.
I think you are missing a ':' in INSERT VALUES for first value binding.
CREATE OR REPLACE TRIGGER tgr_customer_insert
AFTER INSERT
ON customers
FOR EACH ROW
BEGIN
-- Insert record into customers table
INSERT INTO cardholder
( card_number,
customer_id,
credit_limit
)
VALUES
( :new.seq_cardholder,
:new.customer_id,
:new.credit_limit
);
END;
If, "seq_cardholder" is a sequence then you have to use as below:
CREATE OR REPLACE TRIGGER tgr_customer_insert
AFTER INSERT
ON customers
FOR EACH ROW
BEGIN
-- Insert record into customers table
INSERT INTO cardholder
( card_number,
customer_id,
credit_limit
)
VALUES
( seq_cardholder.nextval,
:new.customer_id,
:new.credit_limit
);
END;

ORA-24344: success with compilation error - Trigger APEX

I've been working around this trigger and when I run the script it tells me the previous error message. I can't seem to figure out why it won't compile correctly, every pl/sql trigger tutorial seems to have the structure my trigger has. Code is the following:
create
or replace trigger new_artist before insert
on
Artist referencing new as nvartist declare counter number;
begin select
count( * ) into
counter
from
Performer
where
Stage_name = nvartist.Stage_name;
if counter > 0 then signal sqlstate '45000';
else insert
into
Artist
values(
nvartist.Stage_name,
nvartist.Name
);
insert
into
Performer
values(nvartist.Stage_name);
end if;
end;
It checks if the new artist already exists in its supertype (Performer), if it does exist it gives an error if it doesn't it inserts both into artist(Stage_name varchar2, Name varchar2) and Performer(Stage_name). Another subtype of Performer (and sibling to Artist) is Band(Stage_name), which in turn has a relationship with Artist. Why does the compiler yell at me for this trigger?
Thanks in advance
You may want to try this variant (I slightly modified names of your tables).
Creating tables with sample data:
CREATE table test_artist(
stage_name varchar2(100)
, name varchar2(100)
);
create table test_performer(
stage_name varchar2(100)
);
/*inserting test performer on which trigger will rise an error*/
insert into test_performer
select 'performer_1' from dual;
Creating trigger:
create or replace trigger new_artist
before insert
on TEST_ARTIST
referencing new as nvartist
for each row
declare
counter number;
begin
select count(*)
into counter
from test_performer
where Stage_name = :nvartist.Stage_name;
if counter > 0 then
--signal sqlstate '45000' ;
raise_application_error( -20001, 'No insertion with existing Performer');
else
/*you cant update the same table, in other case you'll get
ora-04091 table mutating error.
But nevertheless this values will be inserted by sql triggered this trigger.*/
--insert into test_artist values(:nvartist.Stage_name, :nvartist.Name);
insert into test_performer values(:nvartist.Stage_name);
end if;
end new_artist;
After that this insert will work, cause the is no 'performer_2' in 'test_performer' table:
insert into test_artist
select 'performer_2', 'name_2' from dual;
And this will fail:
insert into test_artist
select 'performer_1', 'name_1' from dual;

SQL - How to create a trigger for two joined tables which is used for inserting

Ok , so I know that inserting information in a view based on two joined tables is impossible.
In order to do so , I need to create a trigger to insert the information in both tables , when an insert is made in that view.
For example :
CREATE VIEW myJoinedView AS
SELECT name,g.value from students
JOIN grades g on g.id=students.id;
The trigger is not working :
CREATE TRIGGER myTrigger
INSTEAD OF INSERT ON myJoinedView
BEGIN
INSERT INTO students
(name,value)
SELECT i.myJoinedView
FROM inserted i
INNER JOIN grades
ON i.id = grades.id
END myTrigger;
Then I'm trying to insert :
INSERT INTO myJoinedView VALUES ('Alex',10);
I don't know if the syntax is correct , I did not find any helpful documentation on this specific type of trigger.
I'm getting this error:
Error(10,46): PLS-00103: Encountered the symbol "end-of-file" when
expecting one of the following: ( begin case declare end exception
exit for goto if loop mod null pragma raise return select update
while with
<< continue close current delete fetch lock
insert open rollback savepoint set sql execute commit forall merge
pipe purge
Any help will be well received.
Thank you!
You need to either perform the inserts separately with separate single table insert or merge statements or by using a multi table insert (insert all) statement. Assuming you have a sequence to generate the id you are joining on for example this code will work in a very rudimentary way, but has some significant issues:
create table students ( id number primary key
, name varchar2(60));
create table grades( id number not null
, value number
, constraint grades_fk1 foreign key (id) references students(id));
create sequence student_id_seq;
create or replace view studentgrades as
select name, value from students s join grades g on s.id = g.id;
create or replace trigger studentgrades_ii_trg
instead of insert on studentgrades
begin
insert all into students(id, name) values (student_id_seq.nextval, name)
into grades(id, value) values (student_id_seq.nextval, value)
select :new.name name, :new.value value from dual;
end;
/
insert into studentgrades values ('Alex',10);
insert into studentgrades values ('Alex',8);
The BIG issue with the above trigger is that every time a grade is inserted for 'Alex' a new student record for 'Alex' is also created instead of reusing the previous student record for 'Alex'. That's probably not the desired behavior. Instead it should probably just insert a new grade record for Alex. One way to acheive this is for the studentgrades view to include the id column from the students table so you can uniquely identify which student to add the grade to, updating the trigger as needed:
create or replace view studentgrades as
select s.id, name, value from students s join grades g on s.id = g.id;
create or replace trigger studentgrades_ii_trg
instead of insert on studentgrades
declare
newid students.id%type;
begin
if :new.id is null then
newid := student_id_seq.nextval;
else
newid := :new.id;
end if;
insert all when :new.id is null
then into students(id, name) values (id, name)
else into grades(id, value) values (id, value)
select newid id, :new.name name, :new.value value from dual;
end;
/
insert into studentgrades values (null, 'Paul',10);
insert into studentgrades values (student_id_seq.currval, 'Paul',8);
However, now what happens if you try this:
insert into studentgrades values (student_id_seq.currval, 'Mary',10);
In this case the name is effectively ignored and Paul gets a new grade so again this isn't quite right. The question is should Paul's name be updated to Mary, or should a new student record for Mary be created, or should an exception be raised?