writing exception handler - sql

Im trying to create an exception handler to prevent null values from being inserted. I also want to make it to where if it is a null value being inserted then the original cost will display inside the message instead.
my two tables are..
create table pitem,
(item_id number,
item_cost number(10,2)
);
and..
create table pitem_audit
(item_id number,
message char(80)
);
and heres the procedure..
create or replace procedure update_item_cost
(iItemId INTEGER,
fNewcost NUMBER) AS
fCurCost NUMBER(10,2);
missing_cost EXCEPTION;
BEGIN
select item_cost INTO fCurCost from pitem
where item_id=iItemId;
IF fCurCost IS null THEN
RAISE missing_cost;
ELSE
UPDATE pitem SET item_cost=fNewcost
where item_id=iItemId;
END IF;
COMMIT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
INSERT INTO pitem_audit VALUES (iItemId, 'Invalid Item identifier.');
COMMIT;
WHEN missing_cost THEN
INSERT INTO pitem_audit VALUES (iItemId, 'Item cost is null.');/*<---heres where I want it to use the original cost of the item saying 'Null cost replaced by original cost of whatever the orignal was*\
COMMIT;
WHEN OTHERS THEN
ROLLBACK;
INSERT INTO pitem_audit VALUES (iItemId, 'Miscellaneous error.');
COMMIT;
END update_item_cost;
/
/*THIS IS THE BLOCK USED TO CALL THE UPDATE_ITEM_COST PROCEDURE AND ALSO HOW I ENTER THE VALUES FOR COST*\
DECLARE
item_ident number;
cost number;
BEGIN
item_ident := 10;
cost :='';
update_item_cost(item_ident, cost);
END;
/
My question is how would I go about writing the exception handler to prevent null values from being entered..should I just do an ALTER TABLE statement and add a NOT NULL constraint? Also how would I get the message under the pitem_audit table to display 'Null cost replaced by original value of [original item_cost]'?
thanks in advance :D

Related

How to include a SUBSELECT in VALUES of INSERT to take values from different row?

I want to make a trigger that will insert a value from a connected row. For example I have a table with 3 rows as below:
I create a trigger that will work once row 3 and 4 are deleted (in this case will be deleted at the same time). And I want to record invnr and extinvnr from row 1 based on idparent=id. I cannot seem to make it work though.
CREATE OR REPLACE TRIGGER LOG_DELETEDPAYMENTS
BEFORE DELETE ON payments
FOR EACH ROW
BEGIN
IF :old.invnr IS NULL THEN
INSERT INTO TABLE_LOG_DELETEDPAYMENTS (table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
values ('payments', :old.invnr, :old.extinvnr, :old.invdate, :old:transactionid, :old.info, :old.createdby, sys_context('userenv','OS_USER'), SYSDATE);
END IF;
END;
How can I incorporate this into the trigger above?
Try it this way:
create or replace TRIGGER LOG_DELETEDPAYMENTS
BEFORE DELETE ON payments
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
Declare
my_invnr PAYMENTS.INVNR%TYPE;
my_extinvnr PAYMENTS.EXTINVNR%TYPE;
Begin
IF :old.INVNR IS NULL THEN
Select INVNR, EXTINVNR
Into my_invnr, my_extinvnr
From PAYMENTS
Where ID = :old.IDPARENT;
--
INSERT INTO TABLE_LOG_DELETEDPAYMENTS (table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
values ('payments', my_invnr, my_extinvnr, :old.invdate, :old:transactionid, :old.info, :old.createdby, sys_context('userenv','OS_USER'), SYSDATE);
END IF;
End;
END;
You should select the values of INVNR and EXTINVNR based on ID - IDPARENT relationship and store it in the variables (my_invnr and my_extinvnr).
Those variables are used in INSERT into the log statement.
Because of the Select ... Into statement that is reading the affected table - trigger would fail with table PAYMENTS is mutating error.
To avoid that (to separate transaction from the table) you should Declare the PRAGMA AUTONOMOUS_TRANSACTION.
There will be two rows inserted into LOG as the trigger runs FOR EACH (deleted) ROW.
Regards...
This assumes your are on release 12c or greater of Oracle database.
CREATE OR REPLACE PACKAGE LOG_DELETEDPAYMENTS_PKG
AS
-- need a locally defined type for use in trigger
TYPE t_payments_tbl IS TABLE OF payments%ROWTYPE INDEX BY PLS_INTEGER;
END LOG_DELETEDPAYMENTS_PKG;
CREATE OR REPLACE PACKAGE BODY LOG_DELETEDPAYMENTS_PKG
AS
BEGIN
-- could also put the trigger code here and pass the type as a parameter to a procedure
NULL;
END LOG_DELETEDPAYMENTS_PKG;
CREATE OR REPLACE TRIGGER LOG_DELETEDPAYMENTS_CT
FOR DELETE ON payments
COMPOUND TRIGGER
l_tab LOG_DELETEDPAYMENTS_PKG.t_payments_tbl;
l_count PLS_INTEGER:= 0;
BEFORE EACH ROW IS
BEGIN
-- capture the deletes in local type
l_count := l_count + 1;
l_tab(l_count).invnr := :old.invnr;
l_tab(l_count).extinvnr := :old.extinvnr;
l_tab(l_count).invdate := :old.invdate;
l_tab(l_count).transactionid := :old.transactionid;
l_tab(l_count).info := :old.info;
l_tab(l_count).createdby := :old.createdby;
l_tab(l_count).idparent := :old.idparent;
l_tab(l_count).id := :old.id;
END BEFORE EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN l_tab.first .. l_tab.COUNT LOOP
IF(l_tab(i).invnr IS NULL) THEN
-- if the invoice number is NULL, then get info from parent
SELECT p.invnr
,p.extinvnr
INTO l_tab(i).invnr
,l_tab(i).extinvnr
FROM TABLE(l_tab) p
WHERE p.id = l_tab(i).idparent;
END IF;
END LOOP;
-- log all deletes
FORALL i IN 1 .. l_tab.COUNT
INSERT INTO LOG_DELETEDPAYMENTS
(table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
VALUES
('payments', l_tab(i).invnr, l_tab(i).extinvnr, l_tab(i).invdate, l_tab(i).transactionid, l_tab(i).info, l_tab(i).createdby, sys_context('userenv','OS_USER'), SYSDATE);
l_tab.delete;
END AFTER STATEMENT;
END LOG_DELETEDPAYMENTS_CT;

How to insert data into table even if trigger fails?

Oracle 11.1
I have custom logging table where I insert data:
CREATE TABLE log_table
(
message VARCHAR2(255),
created_by VARCHAR2(40) NOT NULL,
created_at DATE NOT NULL,
);
I have a trigger that runs on a specific table which does some checkings. My problem is: when the trigger fails, I want to be able to log some data into the log_table.
Trigger:
CREATE OR REPLACE TRIGGER my_trigger
FOR INSERT OR UPDATE OF column
ON my_table
COMPOUND TRIGGER
BEFORE STATEMENT IS
BEGIN
// code
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
IF (/*condition for failing*/) THEN
EXECUTE IMMEDIATE 'INSERT INTO mesaj_ama VALUES (:my_message, :my_user, :my_data)'
USING 'custom error message', SYS.LOGIN_USER, SYSDATE;
RAISE_APPLICATION_ERROR(-20001, 'some error');
END IF;
END BEFORE EACH ROW;
END my_trigger;
/
The following code doesn't work. I tried to use EXECUTE IMMEDIATE maybe to force it, but didn't work. I know that in case of an error, there is automatically a table rollback (which means that the INSERT command is cancelled), but I need a way to do this. Any help?
The concept you're looking for is an Autonomous Trasnaction, eg
CREATE OR REPLACE TRIGGER log_sal
BEFORE UPDATE OF salary ON emp FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO log (
log_id,
up_date,
new_sal,
old_sal
)
VALUES (
:old.employee_id,
SYSDATE,
:new.salary,
:old.salary
);
COMMIT;
END;
Yes, PRAGMA AUTONOMOUS_TRANSACTION seems to be the answer. Here is the working code:
Defined a procedure for logging:
CREATE OR REPLACE PROCEDURE log_error(p_error log_table.message % TYPE) AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO log_table
VALUES (p_error, SYS.LOGIN_USER, SYSDATE);
COMMIT;
END;
and the trigger which calls the procedure:
CREATE OR REPLACE TRIGGER my_trigger
FOR INSERT OR UPDATE OF column
ON my_table
COMPOUND TRIGGER
BEFORE STATEMENT IS
BEGIN
// code
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
IF (/*condition for failing*/) THEN
log_error('custom error message');
RAISE_APPLICATION_ERROR(-20001, 'custom error message');
END IF;
END BEFORE EACH ROW;
END my_trigger;
/

Procedure to check if authorized

So I have this function:
create or replace function get_authorization(
p_pnr in bankcustomer.pnr%type,
p_knr in account.cnr%type)
return number
as
v_authorization bankcustomer.pnr%type;
begin
select count(*)
into v_authorization
from bankcustomer,account
where pnr = p_pnr
and cnr = p_cnr;
return v_authorization;
exception
when no_data_found then
return -1;
end;
This returns 1 or 0 if allowed. I need to do a procedure which adds a row in a table called withdraw and that procedure needs to call get_authorization to check if the customer is allowed. This is what i have so far:
create or replace procedure do_withdraw(
p_pnr in withdraw.pnr%type,
p_belopp in withdraw.belopp%type)
as
declare
authorization exception;
begin
insert into uttag(pnr,amount)
values((select pnr from account),p_amount); /*pnr is foreign key in withdraw*/
if user not in (select get_amount(p_pnr) from dual) then
raise authorization;
end if;
exception
when authorization then
raise_application_error(-20007,'Unauthorized!');
commit;
end;
I get alot of error messages and specifically on declare. I really cant wrap my head around this :(
You have few problems with your code -
create or replace procedure do_withdraw(
p_pnr in withdraw.pnr%type,
p_belopp in withdraw.belopp%type)
as
-- declare -- Declare keyword is not used in procedure
authorization exception;
begin
insert into uttag(pnr,amount)
values((select pnr from account), -- This might return multiple rows, So you have to add a where clause in this query.
p_amount); /*pnr is foreign key in withdraw*/
if user <> get_authorization(p_pnr) then -- You are checking current user with amount which should be corrected.
raise authorization;
end if;
commit; -- Commit should be last sattement of procedure
exception
when authorization then
raise_application_error(-20007,'Unauthorized!');
Rollback; -- In exception you should use Rollabck instead of Commit;
end;
So ive tried what you said and think i know what you mean:
create or replace procedure do_withdraw(
p_pnr in withdraw.pnr%type,
p_amount in withdraw.amount%type)
as
-- declare -- Declare keyword is not used in procedure
authorization exception;
begin
insert into withdraw(pnr,amount)
values((select pnr from account where pnr = p_pnr), -- This might return multiple rows, So you have to add a where clause in this query.
p_amount); /*pnr is foreign key in withdraw*/
if user not in (select get_authorization(p_pnr)) then -- You are checking current user with amount which should be corrected.
raise authorization;
end if;
commit; -- Commit should be last sattement of procedure
exception
when authorization then
raise_application_error(-20007,'Unauthorized!');
Rollback; -- In exception you should use Rollabck instead of Commit;
end;
Now i get error: Line/Col: 11/51 PLS-00103: Encountered the symbol ")" when expecting one of the following:. ( , * % & - + /

sql insert procedure: insert values into table and control if value is existing in another table

I have little problem. I am trying to insert value into table. This is working. But I would like to control if value id_trainer is existing in another table. I want this -> execute insertClub(1, 5, 'someName'); -> and if id_trainer 5 not exists in table Trainer, procedure gives me message about this. (I tried to translate it into eng. lng., so you can find some gramm. mistakes)
create or replace procedure insertClub
(id_club in number, id_trainer in number, clubName in varchar2)
is
begin
declare counter number;
select count(*) into counter from trianer tr where tr.id_trainer = id_trainer;
if counter = 0 then
DBMS_OUTPUT.PUT_LINE('Trainer with this ID not exists');
end if;
insert into club values(id_club, id_trainer, clubName);
exception
when dup_val_on_index then
DBMS_OUTPUT.PUT_LINE('Dup ID');
end;
/
There is some error in the procedure. Please run below code to create procedure:
create or replace procedure insertClub
(id_club in number, id_trainer in number, clubName in varchar2)
is
counter number;
begin
select count(*) into counter from trianer tr where tr.id_trainer = id_trainer;
if counter = 0 then
DBMS_OUTPUT.PUT_LINE('Trainer with this ID not exists');
end if;
insert into club values(id_club, id_trainer, clubName);
exception
when dup_val_on_index then
DBMS_OUTPUT.PUT_LINE('Dup ID');
end;
/

insert trigger in pl/sql

I cant find the right structure for the following question in pl/sql:
need a trigger on “products table” that will check the price before inserting a new product, product price must not exceed 4000$.
CREATE or REPLACE TRIGGER pro
BEFORE UPDATE OF price
ON products
FOR EACH ROW
declare
pr products.price%type;
BEGIN
if pr < 4000 then
INSERT INTO products VALUES (:old.product_ID,:old.price);
end if;
END;
Please help
Use check constraint instead of trigger:
create table products (price number);
ALTER TABLE PRODUCTS ADD CONSTRAINT check_price CHECK (price < 4000);
Test:
insert into products values (5000) => ERROR
Edit: If you insist on trigger version:
CREATE or REPLACE TRIGGER pro BEFORE insert or UPDATE OF price ON products
FOR EACH ROW
BEGIN
if :new.price > 4000 then
raise_application_error(-20101, 'Price exceeds 4000.');
end if;
END;
The only reason you would bother with a trigger for this instead of a check constraint is to control the error message. And remember that a trigger assumes that the operation is happening, so to stop the operation your tool is to raise an exception.
CREATE OR REPLACE TRIGGER pro
BEFORE INSERT OR UPDATE
ON products
FOR EACH ROW
BEGIN
if :new.price > 4000 then
RAISE_APPLICATION_ERROR(-20001,'Price exceeds maximum permitted value') ;
end if;
END;
if you don't like to raise errors and just keep old values as I think so, use this code:
CREATE or REPLACE TRIGGER pro
BEFORE UPDATE OF price
ON products
FOR EACH ROW
declare
pr products.price%type;
BEGIN
if :new.price > 4000 then
:new.price := :old.price;
:new.product_ID := :old.product_ID;
end if;
END;