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

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.

Related

How to successfully reference another table before insert with a trigger

I'm trying to create a trigger to validate if a new entry in the table registraties (registrations) contains a valid MNR (employee number) but I'm getting stuck on the part where I'm referencing the table medewerkers (employees).
Could someone help me out?
CREATE OR REPLACE TRIGGER t_MNRcontrole
BEFORE INSERT OR UPDATE
ON registraties
DECLARE
MNR_medewerkers number (SELECT MNR FROM MEDEWERKERS);
FOR EACH ROW
BEGIN
IF :new.MNR <> MNR_medewerkers
THEN raise_application_error(-20111, 'Medewerker niet herkend!');
END IF;
END;
Error message received is
ORA-24344: success with compilation error
The PL/SQL assignment operator is :=, or select x into y from z to populate from a SQL query.
FOR EACH ROW is part of the trigger spec, not the PL/SQL code.
If :new.mnr is not present in the parent table, you will get a no_data_found exception, not a mismatched variable.
It's good practice for error messages to include details of what failed.
In programming, we use indentation to indicate code structure.
A fixed version would be something like:
create or replace trigger trg_mnrcontrole
before insert or update on registraties
for each row
declare
mnr_medewerkers medewerkers.mnr%type;
begin
select mw.mnr into mnr_medewerkers
from medewerkers mw
where mw.mnr = :new.mnr;
exception
when no_data_found then
raise_application_error(-20111, 'Medewerker '||:new.mnr||' niet herkend!');
end;
However, we can implement this kind of check better using a foreign key constraint, for example:
alter table registraties add constraint registraties_mw_fk
foreign key (mnr) references medewerkers.mnr;
MNR_medewerkers number (SELECT MNR FROM MEDEWERKERS);
will always fail because its not a NUMBER, unless your table happens to only have one single entry and even then I am not sure PLSQL will allow it to pass.
The more standard case for this would be to first declare the number, then in the codeblock you do a SELECT INTO along with a WHERE clause where you make sure to only pick one specific row from the table. Then you can compare that number with the new one.
If however you are not trying to compare to one specific row, but are instead checking if the entry exists in that table.
BEGIN
SELECT 1
INTO m_variable
FROM table
WHERE MNR = :new.MNR;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
m_variable = 1;
WHEN OTHERS THEN
m_variable = 0;
END;
Declare the m_variable beforehand, and then check if its 0 then report the error.
The too_many_rows is in case there is more than one row in the table with this MNR, and the OTHERS is there for the NO_DATA_FOUND, but I use OTHERS to handle everything else that could happen but probably wont.
Btw this is a code block to be included within the main code block, so between your BEGIN and IF, then just change the IF to check if the variable is 0.

How to write a trigger that checks an updated value in Oracle?

For an assignment I need to write an Oracle SQL trigger that prevents the StdBalance column in the STUDENT table from exceeding 500. Normally I would just use a check constraint to enforce this however I am being forced to write a trigger for it. As expected Oracle is throwing a mutating error due to me using SELECT in an update trigger and will not let me update the StdBalance value with anything. Does anyone have any idea how I could work around this? Here is the code for the trigger.
CREATE OR REPLACE TRIGGER Balance_Trigger
BEFORE UPDATE ON STUDENT
FOR EACH ROW
DECLARE
Current_Balance NUMBER;
BEGIN
SELECT :new.StdBalance
INTO Current_Balance
FROM STUDENT
WHERE :new.stdID = StdID;
IF Current_Balance > 500
THEN Raise_Application_error(-20007, 'You cannot exceed an unpaid balance of $500');
end if;
end;
/
show error;
Just use :
Current_Balance := :new.StdBalance;
Instead of
SELECT :new.StdBalance
INTO Current_Balance
FROM STUDENT
WHERE :new.stdID = StdID;
to suppress the mutating error.
P.S. Even if such assignments are used in these cases, as David Faber warned, there's no need to return a value for Current_Balance, :new.StdBalance may be used directly in comparison for IF :new.StdBalance > 500 ....

SQLITE: stop execution if select returns specific value

Is there any way to write an SQL input file for sqlite that would somehow "throw" an error, eg. exited the transaction with rollback, if a condition isn't met?
I have a script that is supposed to do something, but only if there is a certain row in one table. If it's not there, the execution of the script might have fatal results and corrupt the db.
The script is only started on demand right now, but I would prefer to add a fail-safe which would prevent its execution in case there is some issue.
Basically what I need is something like
/* IF */ SELECT value FROM meta WHERE key = 'version' /* != hardcoded_version_string THROW SOME EXCEPTION */
Is there any way to accomplish that? In Postgre / Oracle this could be done using PLSQL but I am not sure if sqlite support any such a thing?
Triggers can use the RAISE function to generate errors:
CREATE VIEW test AS SELECT NULL AS value;
CREATE TRIGGER test_insert
INSTEAD OF INSERT ON test
BEGIN
SELECT RAISE(FAIL, 'wrong value')
WHERE NEW.value != 'fixed_value';
END;
INSERT INTO test SELECT 'fixed_value';
INSERT INTO test SELECT 'whatever';
Error: wrong value
Is there any way to write an SQL input file for sqlite that would
somehow "throw" an error, eg. exited the transaction with rollback, if
a condition isn't met?
One workaround may be to create dummy table and explicitly violate NULL constraint:
CREATE TABLE meta("key" VARCHAR(100));
INSERT INTO meta("key") VALUES ('version');
CREATE TEMPORARY TABLE dummy(col INT NOT NULL);
Transaction:
BEGIN TRANSACTION;
INSERT INTO dummy(col)
SELECT NULL -- explicit insert of NULL
FROM meta
WHERE "key" = 'version';
-- Error: NOT NULL constraint failed: dummy.col
-- rest code
INSERT INTO meta("key")
VALUES ('val1');
INSERT INTO meta("key")
VALUES ('val2');
-- ...
COMMIT;
SqlFiddleDemo
Keep in mind that SQLite is not procedural language and this solution is a bit ugly.

trigger after on insert, error table mutation upon inserting record

I have created a trigger that will update the Payment table (for bill) when a new record is inserted to the table Enrollment. My trigger is as follows:
CREATE OR REPLACE TRIGGER EnrollFee_trig
AFTER INSERT ON Enrollment
FOR EACH ROW
DECLARE
amount Payment.TotalPrice%TYPE;
id Payment.LearnerID%TYPE;
BEGIN
SELECT SUM(Price) into amount
FROM LearnerEnrollCourse_View
WHERE LearnerID = :NEW.LearnerID
AND Paid = 'N';
SELECT LearnerID into id
FROM Payment
WHERE LearnerID = :NEW.LearnerID
AND PaymentDate IS NULL;
IF SQL%FOUND THEN
UPDATE Payment
SET TotalPrice = amount
WHERE LearnerID = :new.LearnerID
AND PaymentDate IS NULL;
ELSE
INSERT INTO Payment VALUES
(PaymentID_Seq.nextval, :new.LearnerID, '', amount);
END IF;
END;
/
The trigger can be created successfully. But when inserting new record into Enrollment table, there is error saying ' table ENROLLMENT is mutating, trigger/function may not see it'. I want to know more specific about what problem causing this and how can I solve it.
Mutating table exceptions occur when we try to reference the triggering table in a query from within row-level trigger code. See more here
In this instance I suspect ( though I don't know as there is no definition for LearnerEnrollCourse_View ) the problem is caused by this statement :-
SELECT SUM(Price) into amount
FROM LearnerEnrollCourse_View
WHERE LearnerID = :NEW.LearnerID
AND Paid = 'N';
If the LearnerEnrollCourse_view view refers to the Enrollment table, you will get the mutating table error. There are a number of ways round it, moving your trigger code into a statement level trigger and holding the data in package variables is one workaround, in general though, I think using triggers is not the best way to do this, the more triggers you have, the more likely you are to run into this and other problems. Instead, I would have an api package for the enrollment table, and move the trigger code into there.
Good discussion of triggers here

Oracle SQL Trigger

I want to prevent the database from storing any values bigger than 20 into a table.
CREATE OR REPLACE TRIGGER Dont_Allow
AFTER INSERT ON Cities
FOR EACH ROW
WHEN (new.IDCity > 20)
BEGIN
dbms_output.put_line(' Failed to insert ' || :new.IDCity);
delete from orase where IDCity=:new.IDCity;
END;
While this does work in terms of not actually adding anything with an ID > 20, every time the trigger tries to do its magic, this shows up:
ORA-04091: table SYSTEM.ORASE is mutating, trigger/function may not see it
ORA-06512: at "SYSTEM.DONT_ALLOW", line 6
ORA-04088: error during execution of trigger 'SYSTEM.DONT_ALLOW'
What's a proper way of doing what I want?
EDIT:
I've decided to use a trigger for this:
After a new row is inserted into Employees, a trigger checks the new guy's salary and if it's above 21 units / hour, it takes 5% off management's bonus. Lame, but hey - I'm using a trigger to solve a problem I don't have: the outcome won't be pretty.
CREATE OR REPLACE TRIGGER Bite_Bonus
AFTER INSERT ON Employees
FOR EACH ROW
WHEN (new.HourSalary > 20)
BEGIN
update Management set Bonus = Bonus - 5/100 * Bonus;
END;
You shouldn't be using a TRIGGER for that, you should be using a CHECK, like CONSTRAINT city_id_below_20 CHECK (IDCity < 20). You can use ALTER TABLE ADD CONSTRAINT to put it on an existing table.
As TC1 indicated, the proper way to enforce this sort of requirement is to use a constraint.
If you are forced to use the inferior approach because this is a school assignment, you most likely want to raise an exception in your trigger
CREATE OR REPLACE TRIGGER Dont_Allow
BEFORE INSERT OR UPDATE ON Cities
FOR EACH ROW
WHEN (new.IDCity > 20)
BEGIN
RAISE_APPLICATION_ERROR( -20001, 'IDCity cannot exceed 20 so rejecting invalid value: ' || :new.IDCity );
END;
If you need to use a trigger for this, make it a BEFORE INSERT trigger, not an AFTER INSERT - you don't want that insert to happen at all. Trying to "undo" it after the fact is not a good approach.
To abort the insert, all you need to do is raise an exception within that trigger. Probably the best thing for this is to raise an application error.