I have the following error:
ORA-04091: table SYSTEM.ORDINE is mutating, trigger/function may not
see it
On this trigger PL/SQL:
create or replace trigger T_Ordine
after insert on Ordine
for each row
DECLARE
conta number := 0;
t_o exception;
BEGIN
select count(*) into conta
from Cliente join Ordine on Cliente.ID_Cliente = Ordine.ID_CLiente
where Cliente.C_CF_Rivenditore <> Ordine.CF_Rivenditore;
if conta > 0 then
raise t_o;
end if;
EXCEPTION
when t_o then
raise_application_error(-20002,'Un rivenditore non puo ricevere ordini da un cliente non suo');
end;
/
I think that the error caused from the modification of table Ordine in the join with table Cliente.
Your trigger is a little odd.
You've declare it for each row, yet you never use :new to access any of the inserted values.
As far as I can see, there are two ways to fix your trigger:
Make the trigger a statement-level trigger, so that it runs once after the ordine table is inserted into, regardless of how many rows are inserted. To do this, simply delete the line for each row.
Adjust the trigger to only check the inserted order, rather than every order in the table. To do this, replace the SQL query you use to find conta with the following:
select count(*) into conta
from Cliente
where Cliente.ID_Cliente = :new.ID_CLiente
and Cliente.C_CF_Rivenditore <> :new.CF_Rivenditore;
Note that we are no longer querying the Ordine table - the details of the row that has just been inserted are available as :new.column_name. This gets around the ORA-04091 error.
I would recommend the second approach. The query you use to find conta currently searches the whole of the Ordine table, and as your application gains more and more orders, this trigger gets slower and slower as the query searches through more and more data. Also, you probably don't want your application to refuse to take any orders from anyone if it happens that there's one order somewhere in the system where the client's Rivenditore doesn't match the order's Rivenditore.
Incidentally, there's not a lot of point raising the exception t_o, catching it and raising an alternative exception. Just raise the second exception straight away, i.e.:
if conta > 0 then
raise_application_error(-20002,'Un rivenditore non puo ricevere ordini da un cliente non suo');
end if;
Since I am italian, I am a little advantaged in understanding what you are trying to do:
"Ordine" is the table of orders (like product orders).
"rivenditore" means "seller".
"cliente" means customer.
in the "customer" table there is a field (C_CF_Rivenditore) that imposes the seller that should be used for orders issued by the customer.
the "orders" table contains a reference to the customer and a reference to the seller receiving the order.
you simply want to make it impossible to insert an order for a seller different from the one designated for each customer (this is what your error message says), but you don't know how to use :new or :old, so you wrote the test that way (which isn't at all the best method of doing it, since you are re-checking all the orders in the table every time a new order is inserted).
This is what you really want to write:
create or replace trigger T_Ordine
after insert on Ordine
for each row
DECLARE
rivenditore_del_cliente Cliente.C_CF_Rivenditore%type;
BEGIN
select Cliente.C_CF_Rivenditore
into rivenditore_del_cliente
from Cliente
where Cliente.ID_Cliente = :new.ID_CLiente
if rivenditore_del_cliente <> :new.CF_Rivenditore then
raise raise_application_error(-20002,
'Un rivenditore non puo ricevere ordini da un cliente non suo');
end if;
end;
the above trigger might need to be extended with some further checks if some of these are true:
id_cliente is not the primary key of "cliente"
ordine.id_cliente is not mandatory
there isn't a foreign key constraint that ensures that ordine.id_cliente is a valid ID_cliente of the clienti table.
Related
I am creating a book tracking database for myself that holds information about my books and allows me to keep track of who is borrowing them. I am trying to create a trigger on my Checkouts table that runs if a record is added or updated that will determine if a checkout data has been entered or if a checkin date has been entered and change the "available" field in my Books table to "Y" or "N".
I have created a trigger called "update_book_availablility" on my Checkouts table but I keep getting this error:
"PLS-00103: Encountered the symbol 'end-of-file' when expecting one of the following: ( begin case declare and 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 standard pipe purge json_object
Errors: check compiler log"
Here is my trigger code:
CREATE OR REPLACE NONEDITIONABLE TRIGGER "UPDATE_BOOK_AVAILABILITY"
AFTER INSERT OR UPDATE OF ISBN, PersonID, checkout_date, checkin_date
ON Checkouts
FOR EACH ROW
BEGIN
IF :NEW.checkout_date = NULL
THEN
UPDATE Book
SET available = 'N'
WHERE ISBN IN (SELECT :NEW.ISBN FROM Checkouts);
END IF;
END;
Here is an image of my ERD:
ERD
I have been looking into and double checking my trigger syntax, If condition syntax, subquery syntax, and googling this error but have found nothing that has helped. I am new to PL/SQL and would appreciate any help in understanding what I have done wrong or missed.
PLS-00103: Encountered the symbol end-of-file error is SYNTAX ERROR
Copied your trigger and adjusted it to one of my test tables - it works. I removed NONEDITIONABLE and changed trigger table name as well as column names and table/column beeing updated by trigger.
To Do:
Check your syntax again or write the trigger from scratch once more
"...WHERE ISBN IN (SELECT :NEW.ISBN FROM Checkouts)..." selects one fixed value (FOR EACH ROW) :NEW.ISBN of triggering table, better ->> "... WHERE ISBN = :NEW.ISBN ..."
Prety sure that you don't need NONEDITIONABLE trigger for your books tracking app...
Regards...
I am testing the trigger named, "tulockout" listed below, with this alter statement..."alter user testuser account lock;" to see if the trigger log a record of what happened in table, "log_table_changes".
However, certain values are not accurately logging into the table, "log_table_changes". To be specific v_dusr.start_dt is returning NULL when the trigger, "tulockout" fires off after I execute "alter user testuser account lock;" statement.
I am not certain as to why. Can you please assist?
How can I fix this issue? Thanks.
create or replace trigger tulockout
after alter on schema
declare
cursor v_abc is
select du.username, max(us.start_dt)
from dba_users du, user_session us, users_info ui
where ui.db_user_name = du.username
and ui.db_user_name = us.user_name
and ui.db_user_name = ora_login_user;
v_dusr v_abc%ROWTYPE;
begin
if(ora_sysevent = 'ALTER' and v_dusr.username = ora_dict_obj_name and
v_dusr.account_status = 'LOCKED') then
insert into log_table_changes(username,
lastlogin_date,
notes,
execute_date,
script_name
)
values(
v_dusr.username,
v_dusr.start_dt,
ora_dict_obj_type||', '||
ora_dict_obj_name||' has been locked out.',
sysdate,
ora_sysevent
);
end;
You are declaring a cursor, and a record based on that; but you don't ever execute the cursor query or populate the variable.
Your cursor query is currently missing a group-by clause so will error when run, because of the aggregate function. You don't really need to include the user name in the select list though, as you already know that value. You are, though, later referring to the v_duser.account_status field, which doesn't exist in your cursor query/rowtype, so you need to add (and group by) that too.
The trigger also needs to be at database, not schema, level; and unless you intend to record who performed the alter command, you don't ned to refer to ora_login_user - looking that user's status up doesn't seem very helpful.
You don't really need a cursor at all; a select-into would do, something like (assuming there will always be a row returned from the joins to your user_session and users_info tables; which implies they store the username in the same case as dba_users does - although I'm not sure why you are joining to users_info at all?):
create or replace trigger tulockout
after alter on database
declare
v_start_dt user_session.start_dt%TYPE;
v_account_status dba_users.account_status%TYPE;
begin
select du.account_status, max(us.start_dt)
into v_account_status, v_start_dt
from dba_users du
join user_session us on us.db_user_name = du.username
-- join not needed?
-- join users_info ui on ui.db_user_name = us.user_name
where du.username = ora_dict_obj_name
group by du.account_status;
if(ora_sysevent = 'ALTER' and ora_dict_obj_type = 'USER'
and v_account_status = 'LOCKED') then
insert ...
and then use those date and status variables and ora_dict_obj_name(the user that was altered) in the insert.
I've also switched to modern join syntax, and tweaked the conditions a bit.
Untested, but should give you the idea.
You could make it even easier by doing a single insert ... select against those tables, removing the need for local variables.
I am trying to build a simple hotel room check-in database as a learning exercise.
CREATE TABLE HotelReservations
(
roomNum INTEGER NOT NULL,
arrival DATE NOT NULL,
departure DATE NOT NULL,
guestName CHAR(30) NOT NULL,
CONSTRAINT timeTraveler CHECK (arrival < departure) /* stops time travelers*/
/* CONSTRAINT multipleReservations CHECK (my question is about this) */
PRIMARY KEY (roomNum, arrival)
);
I am having trouble specifying a constraint that doesn't allow inserting a new reservation for a room that has not yet been vacated. For example (below), guest 'B' checks into room 123 before 'A' checks out.
INSERT INTO HotelStays(roomNum, arrival, departure, guestName)
VALUES
(123, date("2017-02-02"), date("2017-02-06"), 'A'),
(123, date("2017-02-04"), date("2017-02-08"), 'B');
This shouldn't be allowed but I am unsure how to write this constraint. My first attempt was to write a subquery in check, but I had trouble figuring out the proper subquery because I don't know how to access the 'roomNum' value of a new insert to perform the subquery with. I then also figured out that most SQL systems don't even allow subquerying inside of check.
So how am I supposed to write this constraint? I read some about triggers which seem like it might solve this problem, but is that really the only way to do it? Or am I just dense and missing an obvious way to write the constraint?
The documentation indeed says:
The expression of a CHECK constraint may not contain a subquery.
While it would be possible to create a user-defined function that goes back to the database and queries the table, the only reasonable way to implement this constraint is with a trigger.
There is a special mechanism to access the new row inside the trigger:
Both the WHEN clause and the trigger actions may access elements of the row being inserted, deleted or updated using references of the form "NEW.column-name" and "OLD.column-name", where column-name is the name of a column from the table that the trigger is associated with.
CREATE TRIGGER multiple_reservations_check
BEFORE INSERT ON HotelReservations
BEGIN
SELECT RAISE(FAIL, "reservations overlap")
FROM HotelReservations
WHERE roomNum = NEW.roomNum
AND departure > NEW.arrival
AND arrival < NEW.departure;
END;
i'm creating a trigger to update other table to audit.
My main table that is being audited is "employed" and the audit table is "salary_history". Every a update is made in emplyed table the salary_history must be updated.
I'm working with this trigger in oracle 11g:
CREATE OR REPLACE TRIGGER salary_auditing
AFTER UPDATE ON employed
REFERENCING NEW AS NEW OLD AS OLD
BEGIN
INSERT INTO SALARY_HISTORY(ID, EMPLOYED_ID, OLD_SALARY, NEW_SALARY, DATE_MODIFIED) VALUES (SELECT MAX(ID)+1 FROM SALARY_HISTORY, ID, :OLD.salary, :NEW.salary, SYSDATE)
END salary_auditing
The problema is that when i run this trigger i receive this error:
Relatório de erros -
ORA-04082: referências NEW ou OLD não permitidas nos gatilhos de nível de tabela
04082. 00000 - "NEW or OLD references not allowed in table level triggers"
*Cause: The trigger is accessing "new" or "old" values in a table trigger.
*Action: Remove any new or old references.
In english it means that the NEW or OLD values is not allowed at table nivel.
what modifications i should do?
Oracle has row-level and statement-level triggers. Only row-level triggers have access to the :new and :old pseudorecords. Your trigger is currently defined as a statement-level trigger. You need to add FOR EACH ROW to make it a row-level trigger.
Getting the max(id)+1 in an insert statement is a really really bad idea. Unless you have a single user application, that will cause multiple sessions to get the same value and lead to duplicate key errors. Create a sequence and use that sequence.
Saying REFERENCING NEW AS NEW OLD AS OLD is pointless. Unless you are intending to provide different aliases for the :new and :old pseudo-records, omit that. And you should almost never use different aliases.
I would expect that you want
create sequence seq_salary_history_id
start with <<value larger than what is currently in the table>>;
CREATE OR REPLACE TRIGGER salary_auditing
AFTER UPDATE ON employed
FOR EACH ROW
BEGIN
INSERT INTO SALARY_HISTORY(ID,
EMPLOYED_ID,
OLD_SALARY,
NEW_SALARY,
DATE_MODIFIED)
VALUES (seq_salary_history_id.nextval,
:new.ID,
:OLD.salary,
:NEW.salary,
SYSDATE);
END salary_auditing;
If you only want the trigger to fire when you are updating the salary column (since that's the only thing you're tracking history on), add an OF salary to your trigger declaration
CREATE OR REPLACE TRIGGER salary_auditing
AFTER UPDATE OF salary ON employed
FOR EACH ROW
I am a newbie in PLSQL and I would like to create a trigger that checks first if there is a record in a table before making an update.
The code I got so far is:
CREATE OR REPLACE TRIGGER table_bu
BEFORE UPDATE ON employee
FOR EACH ROW
DECLARE
v_employee_id:=employee.employee_ID%TYPE;
BEGIN
SELECT employee_id INTO v_employee_id FROM employee;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20001,'data not found');
END;
How I can create a trigger that checks up if a record exists in the table and if it does not exists does not allow the update.
My table estructure is:
employee_id NUMBER
employee_name VARCHAR(20)
employee_salary NUMBER
...
Thanks
You are on a wrong way. The trigger as it is will throw runtime 'Mutating table' error even after fixing syntax error - you missed semicolon after raise_application_error(also it should take 2 arguments, not one). Correct syntax :
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20001, 'data not found'); -- 1st parameter -error code
Update
As far as I understand the updated version of the question, you want to show error if record doesn't exist. The problem with row level trigger approach is that it won't be executed if nothing is found due to condition in WHERE. The simplest way is to check number of rows affected on client side and raise an error there. Or you can write a procedure that checks sql%rowcount after executing desired update, and then throw an exception if it's 0.
If you prefer to do in a hard way, you can create package variable which of type employee.employee_ID%TYPE, before update statement level trigger that resets variable (say set it to null), after update row level trigger that sets this variable to NEW.employee_ID, and after update statement level trigger that throws an exception if the variable is null. Note: this will properly work for individual updates only.
"How I can create a trigger that checks up if a record exists in the table and if it does not exists does not allow the update."
There is really only one practical way to do this - use a referential constraint (foreign key).