PL/SQL Trigger Issues - sql

I am trying to write a trigger to populate a table containing information on an employee's updated salary. I'm having a problem that I can't quite wrap my head around at the moment.
Here is the table to be populated:
drop table SalUpdates cascade constraints;
create table SalUpdates(
SalSSN char(9),
newSalary decimal(10,2),
oldSalary decimal(10,2)
);
This is my trigger:
create or replace trigger t1
after update of salary on employee
for each row
begin
insert into SalUpdates values (:old.Ssn, :new.salary, :old.salary);
end;
The trigger compiles with no issues, but when I try to run this update, Oracle tells me my trigger is invalid. What could be causing this?
update employee
set salary=4000
where ssn='123456789';

You've shown the code in chunks. but it seems you're running what you've shown together as a script, initially without the update:
drop table SalUpdates cascade constraints;
create table SalUpdates(
SalSSN char(9),
newSalary decimal(10,2),
oldSalary decimal(10,2)
);
create or replace trigger t1
after update of salary on employee
for each row
begin
insert into SalUpdates values (:old.Ssn, :new.salary, :old.salary);
end;
When run as a script in SQL Developer the script output window shows:
drop table SalUpdates cascade constraints
Error report -
ORA-00942: table or view does not exist
00942. 00000 - "table or view does not exist"
*Cause:
*Action:
Table SALUPDATES created.
Trigger T1 compiled
If you then add the update statement to the script:
drop table SalUpdates cascade constraints;
create table SalUpdates(
SalSSN char(9),
newSalary decimal(10,2),
oldSalary decimal(10,2)
);
create or replace trigger t1
after update of salary on employee
for each row
begin
insert into SalUpdates values (:old.Ssn, :new.salary, :old.salary);
end;
update employee
set salary=4000
where ssn='123456789';
you get:
Table SALUPDATES dropped.
Table SALUPDATES created.
Trigger T1 compiled
Errors: check compiler log
If you then try to run the update on it's own (as a statement instead of a script; or by selecting that test and running as a script) you do indeed get:
SQL Error: ORA-04098: trigger 'MYSCHEMA.T1' is invalid and failed re-validation
04098. 00000 - "trigger '%s.%s' is invalid and failed re-validation"
*Cause: A trigger was attempted to be retrieved for execution and was
found to be invalid. This also means that compilation/authorization
failed for the trigger.
*Action: Options are to resolve the compilation/authorization errors,
disable the trigger, or drop the trigger.
If you query the user_errors view, or run show errors, you'll see:
PLS-00103: Encountered the symbol "UPDATE"
The problem is that you aren't completing the create trigger statement properly. The update is being seen as part of the same PL/SQL block; an invalid part, but still included.
When you have a PL/SQL block you have to terminate it with a slash, as it explains in the SQL*Plus documentation (which mostly applies to SQL Developer too):
SQL*Plus treats PL/SQL subprograms in the same manner as SQL commands, except that a semicolon (;) or a blank line does not terminate and execute a block. Terminate PL/SQL subprograms by entering a period (.) by itself on a new line. You can also terminate and execute a PL/SQL subprogram by entering a slash (/) by itself on a new line.
SQL Developer doesn't complain if the last block in a script doesn't have a terminating slash though, so your original script (without the update) works; in SQL*Plus it would sit at a prompt. It kind of infers that it should be there - trying to be helpful. When you add the update statement it is no longer the end of the script so that doesn't apply.
If you add a slash to your script between the PL/SQL code and the following SQL statement it all works:
drop table SalUpdates cascade constraints;
create table SalUpdates(
SalSSN char(9),
newSalary decimal(10,2),
oldSalary decimal(10,2)
);
create or replace trigger t1
after update of salary on employee
for each row
begin
insert into SalUpdates values (:old.Ssn, :new.salary, :old.salary);
end;
/
update employee
set salary=4000
where ssn='123456789';
and you now see:
Table SALUPDATES dropped.
Table SALUPDATES created.
Trigger T1 compiled
1 row updated.

Related

Creating a trigger with SELECT Function

Good day everyone,
I'm having trouble making my trigger work. As far as the functionality of the body and how it behaves, it does exactly as I intended for it to behave. However, when I start to fire the trigger, it returns an error in which Triggers should not have a SELECT statement from inside the main body. I'm still fairly new to coding and how to materialize the idea in my head into code. Hopefully someone could point me in a right direction on how change the Trigger I would like to have as a final result. Please see below script.
Update: Expected result would be whenever a user UPDATE a row and INSERT a new record via the application or job being run in the background, S1_HOVER_REPORT column would be updated with the value from the SELECT script and use the data from the S1_HOVER case result.
(Edit: I have updated the details of the problem above, added the Table being used and Error return)
Table: SITE
Column Name Type
------------------------------
ID VARCHAR2(14)
NAME VARCHAR2(70)
TYPE_CODE VARCHAR2(2)
PARENT VARCHAR2(14)
S1_HOVER_REPORT VARCHAR2(14)
CREATE OR REPLACE TRIGGER MESS.S1_HOVER_REPORT
AFTER INSERT OR UPDATE ON MESS.SITE
FOR EACH ROW
BEGIN
UPDATE (SELECT S1.ID,
S1.NAME,
S1.TYPE_CODE,
S1.PARENT AS PARENT1,
S2.PARENT AS PARENT2,
S1.S1_HOVER_REPORT,
CASE
WHEN (S1.TYPE_CODE = 'H2') THEN S1.PARENT
WHEN (S1.TYPE_CODE = 'S1') THEN S2.PARENT
ELSE S1.ID
END AS S1_HOVER
FROM SITE S1,
(SELECT ID,
NAME,
PARENT,
TYPE_CODE
FROM site
WHERE type_code='H2') S2
WHERE S1.PARENT=S2.ID
OR S1.ID = S2.PARENT) S3
SET S3.S1_HOVER_REPORT = S3.S1_HOVER;
END;
Error returned when Trigger fired:
Error report -
SQL Error: ORA-01779: cannot modify a column which maps to a non key-preserved table
ORA-06512: at "MES.S1_HOVER_REPORT", line 2
ORA-04088: error during execution of trigger 'MES.S1_HOVER_REPORT'
01779. 00000 - "cannot modify a column which maps to a non key-preserved table"
*Cause: An attempt was made to insert or update columns of a join view which
map to a non-key-preserved table.
*Action: Modify the underlying base tables directly.
(Update: I have included the updated trigger and it's now compiling without any issue, but I'm having errors whenever I try updating a record)
CREATE OR REPLACE TRIGGER MESS.S1_HOVER_REPORT
BEFORE INSERT OR UPDATE ON MESS.SITE
FOR EACH ROW
DECLARE
v_S1_HOVER_REPORT VARCHAR2(14);
BEGIN
SELECT CASE
WHEN (S1.TYPE_CODE = 'H2') THEN S1.PARENT
WHEN (S1.TYPE_CODE = 'S1') THEN S2.PARENT
ELSE (S1.ID)
END AS S1_HOVER
INTO v_S1_HOVER_REPORT
FROM SITE S1,
(SELECT ID,
NAME,
PARENT,
TYPE_CODE
FROM site
WHERE type_code='H2') S2
WHERE S1.PARENT=S2.ID
OR S1.ID = S2.PARENT;
:NEW.S1_HOVER_REPORT := v_S1_HOVER_REPORT;
END;
Error report -
SQL Error: ORA-04091: table MES.SITE is mutating, trigger/function may not see it
ORA-06512: at "MES.S1_HOVER_REPORT", line 4
ORA-04088: error during execution of trigger 'MES.S1_HOVER_REPORT'
04091. 00000 - "table %s.%s is mutating, trigger/function may not see it"
*Cause: A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
*Action: Rewrite the trigger (or function) so it does not read that table.
Firstly from the error message
An attempt was made to insert or update columns of a join view which map to a non-key-preserved table.
S3 is the veiw ( you are creating the view by doing a select inside an update statment). You can try and change this to have key preservation but I really wouldn't know how.
The error suggests updating the base tables not the view. So as mentioned in the comments :old and :new are your friend.
:OLD holds all the values of the table the trigger is created on BEFORE the update (null if insert)
:NEW holds all the values of the table the trigger is created on AFTER the update / insert.....
So if I understand what you want to do correctly you would need to...
declare a variable eg v_S1_hover_report
do your select returning whatever value you need into this variable
set the value in your site table by doing
:NEW.S1_HOVER_REPORT := v_S1_hover_report
By setting this value into the :NEW object when a commit happens it will be committed to the database. This completely removes the need for an update statement in the trigger.
You can also use :NEW.id in your select statement to filter it down to the record you are updating if it is helpfull
CREATE OR REPLACE TRIGGER MESS.S1_HOVER_REPORT
AFTER INSERT OR UPDATE ON MESS.SITE
FOR EACH ROW
v_test varchar2(10);
BEGIN
select 'Your value' into v_test from dual;
:new.s1_hover_report := v_test;
END;
OR
CREATE OR REPLACE TRIGGER MESS.S1_HOVER_REPORT
AFTER INSERT OR UPDATE ON MESS.SITE
FOR EACH ROW
v_test varchar2(10);
BEGIN
select 'Your value' into :new.s1_hover_report from dual;
END;

When is FOR EACH ROW not needed in a BEFORE INSERT TRIGGER in Oracle?

I am new to PLSQL in Oracle. When I am learning about triggers, I have read from this source https://www.techonthenet.com/oracle/triggers/before_insert.php which says that when I create a BEFORE INSERT Trigger in Oracle, the FOR EACH ROW is NOT always needed, hence the syntax is enclosed by square brackets [ ]. I have written this simple trigger:
CREATE OR REPLACE TRIGGER enroll_time
BEFORE INSERT
ON ENROLL
FOR EACH ROW
BEGIN
:new.addtime := sysdate;
END;
/
If I remove the FOR EACH ROW in the above, I actually get an error:
Error report -
ORA-04082: NEW or OLD references not allowed in table level triggers
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.
From the error message, it seems like if I use :new.[column_name], then FOR EACH ROW must have to exist. Why is this? Is there any example that FOR EACH ROW is NOT needed in a BEFORE INSERT TRIGGER in Oracle?
Is there any example that FOR EACH ROW is NOT needed in a BEFORE INSERT TRIGGER in Oracle?
Simple example of statement level trigger:
CREATE TABLE test_table(col VARCHAR2(10));
CREATE OR REPLACE TRIGGER enroll_time
BEFORE INSERT
ON ENROLL
BEGIN
INSERT INTO test_table(col)
SELECT 1 FROM dual;
END;
/
I highly recommend to read about compound trigger to understand when each part is fired.
Basically, if you need to use :OLD or :NEW pseudotables, you need a row level trigger. An example of a statement level trigger would be inserting a record into a table when another table is effected.

Why does my SQL trigger statement not appear in DBMS_OUTPUT?

I'm trying to get my head around triggers, but I'm getting errors
Error(2,4): PL/SQL: SQL Statement ignored
Error(2,8): PL/SQL: ORA-00922: missing or invalid option
when creating the following trigger:
CREATE TRIGGER TableTrigger
AFTER UPDATE ON TestTable
FOR EACH ROW
BEGIN
set serveroutput on format wrapped;
DBMS_OUTPUT.put_line('TABLE UPDATED!');
END;
Which works on the following table:
CREATE TABLE TestTable
(
test1 INT,
test2 INT,
test3 INT,
PRIMARY KEY (test1)
);
I'm not sure what to do, does anyone have any suggestions?
Using DBMS_OUTPUT in triggers is not best practice. If you want to see that something was done create a logging table or an audit history table or set auditing of update on for that table.
DBMS_OUTPUT is useful when you are running a PL/SQL procedure or package from SQLPlus or other IDE.
Different versions of SQL may or may not show you the output of the buffer from a trigger.

Oracle SQL trigger - DBMS_OUTPUT.PUT_LINE

I'm creating a trigger within my database, I came across two error that I am not able to fix, I'm pretty sure that those two are relating to my use of DBMS_OUTPUT.PUT_LINE, the rest of the statement does not cause any errors, although it had before.
Errors:
Error(5,3): PL/SQL: SQL Statement ignored
Error(5,15): PL/SQL: ORA-00903: invalid table name
Code:
CREATE TRIGGER INVOICES
BEFORE INSERT OR UPDATE ON BRUINVOICE
FOR EACH ROW
BEGIN
IF :new.BRU_DATE < :new.BRU_PAID_DATE THEN
DBMS_OUTPUT.PUT_LINE('You cannot do that');
ELSE
INSERT INTO table BRUINVOICE
values
from inserted;
END IF;
END;
Check constraints are a better choice (performance-wise) than triggers when it comes to record level validation:
ALTER TABLE bruinvoice
ADD CONSTRAINT validate_bru_date CHECK (BRU_DATE < BRU_PAID_DATE);
Inserting invalid data will raise an error message like the following:
scott#ORCL> insert into bruinvoice values ('21-DEC-14','20-DEC-14');
insert into bruinvoice values ('21-DEC-14','20-DEC-14')
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.VALIDATE_BRU_DATE) violated
I fully agree with cstotzer, a check constraint is much better in your situation at should be the preferred way of doing it. However, just for information this would be the trigger syntax:
CREATE TRIGGER INVOICES
BEFORE INSERT OR UPDATE ON BRUINVOICE
FOR EACH ROW
BEGIN
IF :new.BRU_DATE < :new.BRU_PAID_DATE THEN
RAISE_APPLICATION_ERROR(-20001, 'You cannot do that');
END IF;
END;
You don't need any ELSE, your INSERT or UPDATE will be simply executed in this case.

SQL trigger error - invalid trigger

I'm using pl\sql developer and I have a report table with a number(38) ID column.
I want to keep track of all updates for this table so I created another table like this:
CREATE TABLE reportUpdate (report_id number(38), updatedate number(32));
And I created a trigger:
CREATE or REPLACE TRIGGER BeforeUpdateReport
BEFORE
UPDATE ON REPORT
FOR EACH ROW
Begin
INSERT INTO reportUpdate
Values(old.ID,sysdate);
END;
And when I run it, I get an error, saying: trigger 'SYSTEM.BEFOREUPDATEREPORT' is invalidand failed re-validation.
Can someone please help
You can use show errors after you see compiled with warnings, or query the user_errors view to see what is wrong later.
One obvious thing is that you haven't prefixed the old reference with a colon:
CREATE or REPLACE TRIGGER BeforeUpdateReport
BEFORE
UPDATE ON REPORT
FOR EACH ROW
Begin
INSERT INTO reportUpdate
Values(:old.ID,sysdate);
END;
/
It's also better to specify the target table fields in the insert statement:
INSERT INTO reportUpdate (report_id, updatedate)
Values(:old.ID,sysdate);
But you have update_date defined in your table creation script as number(32), which doesn't make sense. As #realspirituals pointed out, it should be:
CREATE TABLE reportUpdate (report_id number, updatedate date);