oracle triggers error ORA-01427 & error ORA-04091 - sql

I have the following tables:
FACULTY table
CREATE TABLE "FACULTY"
( "FACULTY_ID" NUMBER(3,0),
"FACULTY_NAME" VARCHAR2(30),
"FACULTY_DEAN" VARCHAR2(30),
CONSTRAINT "FACULTY_PK" PRIMARY KEY ("FACULTY_ID") ENABLE
)
COURSE table
CREATE TABLE "COURSE"
( "COURSE_ID" NUMBER(5,0),
"COURSE_NAME" VARCHAR2(50),
"COURSE_LEVEL" NUMBER,
"FACULTY" NUMBER,
CONSTRAINT "COURSE_PK" PRIMARY KEY ("COURSE_ID") ENABLE
)
so now i want to achieve two things
(1) when a faculty_id is updated on the faculty table. a trigger will fire and update the corresponding rows in the course table with new faculty_id. it will also store the old faculty_id value, name of course, and the date in which the operation is performed in a course_log table.
Below is what I got
create or replace trigger update_faculty
after update on faculty
for each row
begin
insert into course_log
values (:old.faculty_id,
(select course_name
from course
where faculty=:old.faculty_id),
sysdate);
update course
set faculty=:new.faculty_id
where faculty=:old.faculty_id;
end;
But I get the following error.
error ORA-01427: single-row subquery returns more than one row ORA-06512: at "SYSTEM.UPDATE_FACULTY", line 2 ORA-04088: error during execution of trigger 'SYSTEM.UPDATE_FACULTY'
any ideas on how to solve it?
(2) Write a trigger that fires when try change the course_id attribute in the course table, which will check whether the value already exists in the course table and will update successfully if it is a new value. If the value already exists in any row, the trigger will throw an application error saying "The course_id already exists! Update not successful."
below is my query
CREATE OR REPLACE TRIGGER "UPDATE_COURSE_ID"
after update on course
for each row
declare
error number;
begin
select count(*)
into error
from course
where course_id=:new.course_id;
if error > 0 then
raise_application_error (-20000,'The course_id already found! Update not success');
end if;
if error = 0 then
update course set course_id=:new.course_id where course_id=:old.course_id;
end if;
end;
But I got this error
error ORA-04091: table SYSTEM.COURSE is mutating, trigger/function may not see it ORA-06512: at "SYSTEM.UPDATE_COURSE_ID", line 5 ORA-04088: error during execution of trigger 'SYSTEM.UPDATE_COURSE_ID'

For the first question, since you may want to insert multiple rows into the course_log table, you'd need to do something like
create or replace trigger update_faculty
after update on faculty
for each row
begin
-- I'm guessing about the definition of the course_log table
insert into course_log( faculty_id, course_name, log_date )
select :old.faculty_id, course_name, sysdate
from course
where faculty=:old.faculty_id;
update course
set faculty=:new.faculty_id
where faculty=:old.faculty_id;
end;
For the second question, it doesn't make sense to use a trigger. You'd want to use a constraint. And you already have a primary key constraint on course_id in course which is already preventing duplicate course_id values.
Enforcing this sort of thing with triggers is a really poor idea. Since a row level trigger on course cannot query the course table (with the exception of a row-level insert trigger if your insert statements are always of the single-row form INSERT ... VALUES or triggers that use autonomous transactions, neither of which is appropriate here). So if you really wanted to do this with triggers, you'd need to create a package that contained a collection of course_id values, a before statement trigger that initializes the collection, a row-level trigger that adds the :new.course_id to the collection, and an after statement trigger that iterates over the collection and looks for duplicate course_id values. That's a lot of objects to do something that shouldn't be done with triggers in the first place and that is already being done by your constraint. If you're just learning about triggers, I'm guessing that you haven't been taught about packages or collections yet which makes the solution even less appropriate.

Related

Trigger created with compiling errors on unique value

I'm trying to create a trigger that checks the value of a column whenever I have an insert or an update on the table, the value of columnX must be unique:
tableX(ID, ..., columnX)
CREATE or replace TRIGGER tableX_uk
BEFORE INSERT OR UPDATE ON tableX
FOR EACH ROW
BEGIN
if(:new.columnX in (select T.columnX from tableX T)) then
Raise_Application_Error(-20001, 'Already existing');
end if;
End;
It shows that the trigger is created with compiling errors.
I couldn't find any error here, can someone help me please ? Thank you !
It shows that the trigger is created with compiling errors. I couldn't find any error here
Errors for stored PL/SQL can be found in user/all/dba_errors. Desktop tools such as SQL Developer, Toad, PL/SQL Developer etc will display them automatically but that's where they get the details from.
DBFiddle
In this case the first error is from
if(:new.columnX in (select t.columnX from tableX t))
which gives
PLS-00405: subquery not allowed in this context
because if x in (select...) isn't valid PL/SQL syntax. You have to do the select as a separate step. Fixing that will give you code that at least compiles, but still isn't ideal:
create or replace trigger tablex_uk_trg
before insert or update on tablex
for each row
declare
l_columnx_check number := 0;
begin
select count(*) into l_columnx_check
from tablex t
where t.columnx = :new.columnx
and rownum = 1;
if l_columnx_check > 0 then
raise_application_error(-20001, 'Value '||:new.columnx||' already exists');
end if;
end;
It's not ideal because firstly, a unique constraint is a far more efficient and self-documenting way to enforce uniqueness.
create table tablex (columnx number unique);
or better still
create table tablex (columnx number constraint tablex_uk unique);
or if it's the primary key
create table tablex (columnx number constraint tablex_pk primary key);
Now, anyone checking the table definition will see the unique constraint, the optimiser will use it in queries, it has a standard error code ORA-00001: unique constraint (WILLIAM.TABLEX_UK) violated and so on.
Secondly, the update part of the trigger won't work anyway. Oracle won't let a row-level update trigger query its own table:
insert into tablex (columnx) values (1);
update tablex set columnx = 1;
ORA-04091: table WILLIAM.TABLEX is mutating, trigger/function may not see it
ORA-06512: at "WILLIAM.TABLEX_UK_TRG", line 4
ORA-04088: error during execution of trigger 'WILLIAM.TABLEX_UK'
DBFiddle
And in any case the logic is missing some checks, because the update in my example should be valid. But I won't go into how you might fix that because the unique constraint is all you need.

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;

How to add constraint to sql table so that table has exactly one row

Parameter table is initially created and one row is added in Postgres.
This table should have always one row, otherwise SQL queries using this table will produce incorrect results. DELETE or INSERT to this table are disallowed, only UPDATE is allowed.
How to add single row constraint to this table?
Maybe DELETE and INSERT triggers can raise an exception or is there simpler way?
The following will create a table where you can only insert one single row. Any update of the id column will result in an error, as will any insert with a different value than 42. The actual id value doesn't matter actually (unless there is some special meaning that you need).
create table singleton
(
id integer not null primary key default 42,
parameter_1 text,
parameter_2 text,
constraint only_one_row check (id = 42)
);
insert into singleton values (default);
To prevent deletes you can use a rule:
create or replace rule ignore_delete
AS on delete to singleton
do instead nothing;
You could also use a rule to make insert do nothing as well if you want to make an insert "fail" silently. Without the rule, an insert would generate an error. If you want a delete to generate an error as well, you would need to create a trigger that simply raises an exception.
Edit
If you want an error to be thrown for inserts or deletes, you need a trigger for that:
create table singleton
(
id integer not null primary key,
parameter_1 text,
parameter_2 text
);
insert into singleton (id) values (42);
create or replace function raise_error()
returns trigger
as
$body$
begin
RAISE EXCEPTION 'No changes allowed';
end;
$body$
language plpgsql;
create trigger singleton_trg
before insert or delete on singleton
for each statement execute procedure raise_error();
Note that you have to insert the single row before you create the trigger, otherwise you can't insert that row.
This will only partially work for a superuser or the owner of the table. Both have the privilege to drop or disable the trigger. But that is the nature of a superuser - he can do anything.
To make any table a singleton just add this column:
just_me bool NOT NULL DEFAULT TRUE UNIQUE CHECK (just_me)
This allows exactly one row. Plus add the trigger #a_horse provided.
But I would rather use a function instead of the table for this purpose. Simpler and cheaper.
CREATE OR REPLACE FUNCTION one_row()
RETURNS TABLE (company_id int, company text) LANGUAGE sql IMMUTABLE AS
$$SELECT 123, 'The Company'$$
ALTER FUNCTION one_row() OWNER TO postgres;
Set the owner to the user that should be allowed to change it.
Give a user permission to ALTER a function
Nobody else change it - except superusers of course. Superusers can do anything.
You can use this function just like you would use the table:
SELECT * FROM one_row();
If you need a "table", create a view (which is actually a special table internally):
CREATE VIEW one_row AS SELECT * FROM one_row();
I guess you will not use the PostgreSQL root user in your application so you could simply limit the permissions of your application user on UPDATE for this table.
An INSERT or DELETE will then cause an Insufficient privilege exception.

how to create a trigger in oracle which will restrict insertion and update queries on a table based on a condition

I have account table as this--
create table account
(
acct_id int,
cust_id int,
cust_name varchar(20)
)
insert into account values(1,20,'Mark');
insert into account values(2,23,'Tom');
insert into account values(3,24,'Jim');
I want to create a trigger which will ensure that no records can be inserted or update in account table having acct_id as 2 and cust_id as 23.
My code is --
create trigger tri_account
before insert or update
on account
for each row
begin
IF (:new.acct_id == 2 and :new.cust_id == 23) THEN
DBMS_OUTPUT.PUT_LINE('No insertion with id 2 and 23.');
rollback;
END IF;
end;
so this trigger is created , but with compilation error.
now when I insert any record with acct_id as 2 and cust_id as 23,it doesent allow.
But I get an error saying
ORA-04098: trigger 'OPS$0924769.TRI_ACCOUNT' is invalid and failed re-validation
I don't understand this.I also want to show a message that dis insertion is not possible.
please Help...
The equality operator in Oracle is =, not ==.
You cannot commit or rollback in a trigger. You can throw an exception which causes the triggering statement to fail and to be rolled back (though the existing transaction will not necessarily be rolled back).
It does not appear that this trigger compiled successfully when you created it. If you are using SQL*Plus, you can type show errors after creating a PL/SQL object to see the compilation errors.
You should never write code that depends on the caller being able to see the output from DBMS_OUTPUT. Most applications will not so most applications would have no idea that the DML operation failed if your trigger simply tries to write to the DBMS_OUTPUT buffer.
Putting those items together, you can write something like
create trigger tri_account
before insert or update
on account
for each row
begin
IF (:new.acct_id = 2 and :new.cust_id = 23) THEN
raise_application_error( -20001, 'No insertion with id 2 and 23.');
END IF;
end;
A trigger is more flexible, but you can also accomplish this through the use of a CHECK CONSTRAINT:
ALTER TABLE account ADD CONSTRAINT check_account CHECK ( acct_id != 2 OR cust_id != 23 )
ENABLE NONVALIDATE;
The NONVALIDATE clause will ensure that the check constraint does not attempt to validate existing data, though it will validate all future data.
Hope this helps.
IF (:new.acct_id = 2 and :new.cust_id = 23) THEN
must be OR, not and.
While using conditional checks you don't need to use colons (:). This will always cause errors.
Note: Exclude the colon only in cases where condition checking is performed.

SQL Oracle Trigger

I'm having problem with simple trigger command. This trigger operation will insert value into table address_rit when a person who studies at RIT is inserted into table person. Here's the syntax for the trigger command:
CREATE OR REPLACE TRIGGER addr
AFTER INSERT ON person
FOR EACH ROW
WHEN (NEW.college = 'RIT')
BEGIN
INSERT INTO address_rit (name, address, state)
VALUES (NEW.name, NEW.address, (SELECT name FROM states WHERE NEW.statecode = states.statecode));
END;
/
The trigger is compiled but with warning. However, further inspection shows that the trigger actually has error. Here's the error from the compilation.
PL/SQL: SQL Statement ignored ERROR
PL/SQL: ORA-00984: column not allowed here ERROR
I'm pretty sure the error is just a syntax error, but I just can't find any solution. Let me know if I need to add more detail. Thank you very much for your help.
At a minimum, you need a colon before NEW
CREATE OR REPLACE TRIGGER addr
AFTER INSERT ON person
FOR EACH ROW
WHEN (NEW.college = 'RIT')
BEGIN
INSERT INTO address_rit (name, address, state)
VALUES (:NEW.name,
:NEW.address,
(SELECT name
FROM states
WHERE :NEW.statecode = states.statecode));
END;
/
I'm also assuming that the query against the STATES table is always going to return exactly 1 row. If the database is properly normalized, though, I would expect that all the tables would have a STATECODE column rather than a STATE column and that there would be foreign keys between both PERSON and ADDRESS_RIT that reference the STATECODE column in STATES. But, if the database is properly normalized, I would also expect that you wouldn't have an ADDRESS_RIT table that duplicated the data in PERSON. Instead, ADDRESS_RIT really ought to be a view on the PERSON table.