Raise Application Error in Trigger and still update table - sql

I'm facing some problem here, when the customer did not make payment in 60 days. The trigger will UPDATE the customer table and still RAISE the
error -20003.
What I've realize is that the update function will be canceled if the raise exception takes place.
Is there any way to go around doing this?
CREATE OR REPLACE TRIGGER cut_supply
BEFORE INSERT ON reading
FOR EACH ROW
DECLARE
cust_ID BILL.custID%TYPE;
sent_date BILL.sentDate%TYPE;
payment_date BILL.paymentdate%TYPE;
bad_Status CUSTOMER.badStatus%TYPE;
first_name CUSTOMER.firstName%TYPE;
last_name CUSTOMER.lastName%TYPE;
NRIC_No CUSTOMER.NRIC%TYPE;
pRate_ID CUSTOMER.pRateID%TYPE;
no_new_reading EXCEPTION;
CURSOR cust_cursor IS
SELECT b.custID, b.sentDate, b.paymentDate, c.badStatus, c.firstName, c.lastName, c.NRIC, c.pRateID
FROM bill b, customer c, reading r
WHERE (sysdate - sentDate) > 20
AND paymentDate is null
AND b.custID = c.custID
AND r.readingID = b.readingID
FOR UPDATE OF c.badStatus;
BEGIN
OPEN cust_cursor;
LOOP
FETCH cust_cursor INTO cust_ID, sent_date, payment_date,
bad_Status, first_name, last_name, NRIC_No, pRate_ID;
IF cust_cursor%NOTFOUND THEN
EXIT;
END IF;
IF cust_cursor%ROWCOUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE( 'No customer found');
ELSE
UPDATE customer
SET pRateID= 801, badStatus='Non Payment'
WHERE CURRENT OF cust_cursor;
RAISE no_new_reading;
END IF;
END LOOP;
CLOSE cust_cursor;
COMMIT;
EXCEPTION
WHEN no_new_reading THEN
RAISE_APPLICATION_ERROR ( -20003,
'Customer ' || first_name||''||last_Name ||
' water supply has been cut off' );
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/

this can be solve in the following manner, create a procedure to update the customer table
create or replace procedure update_customer
as
pragma autonomous_transaction;
begin
update customer c set c.badStatus='Non Payment', c.pRateID=801
where c.custID=
(
select distinct c.custID
FROM bill b, reading r
WHERE (sysdate - sentDate) > 20
AND paymentDate is null
AND b.custID = c.custID
AND r.readingID = b.readingID
);
commit;
end;
/
Use a trigger to call the procedure itself.
CREATE OR REPLACE TRIGGER cut_supply
BEFORE INSERT ON reading
FOR EACH ROW
DECLARE
cust_ID BILL.custID%TYPE;
sent_date BILL.sentDate%TYPE;
payment_date BILL.paymentdate%TYPE;
bad_Status CUSTOMER.badStatus%TYPE;
first_name CUSTOMER.firstName%TYPE;
last_name CUSTOMER.lastName%TYPE;
NRIC_No CUSTOMER.NRIC%TYPE;
reject_new_reading_excep EXCEPTION;
CURSOR cust_cursor IS
SELECT b.custID, b.sentDate, b.paymentDate, c.badStatus, c.firstName, c.lastName, c.NRIC
FROM bill b, customer c, reading r
WHERE (sysdate - sentDate) > 20
AND paymentDate is null
AND b.custID = c.custID
AND r.readingID = b.readingID;
BEGIN
OPEN cust_cursor;
LOOP
FETCH cust_cursor INTO cust_ID, sent_date, payment_date,
bad_Status, first_name, last_name, NRIC_No;
IF cust_cursor%NOTFOUND THEN
EXIT;
END IF;
IF cust_cursor%ROWCOUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE( 'No customer found');
ELSE
update_customer;
RAISE reject_new_reading_excep;
END IF;
END LOOP;
CLOSE cust_cursor;
COMMIT;
EXCEPTION
WHEN reject_new_reading_excep THEN
RAISE_APPLICATION_ERROR ( -20003,
'Customer ' || first_name||''||last_Name ||
' water supply has been cut off' );
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/

Related

Create a procedure to give bonuses to employees

I'm a beginner of PL/SQL.
I need to create a procedure.
The procedure name is sp_emp_bonus that takes 3 parameters:
department_id
job_id
amount
The procedure will update of bonus of employees within the department_id and job_id and increase existing bonus by the amount.
After the update statement, print using DBMS_OUTPUT.PUT_LINE to print out the number of employees affected by update (hint use implicit cursor attribute).
If no employees found then an exception section should capture NO_DATA_FOUND and print employees not found. Also include error handling for WHEN OTHERS ... and print DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM,1,100));
How to write this?
You can use simple update statement and sql%rowcount in procedure as follows:
CREATE OR REPLACE PROCEDURE SP_EMP_BONUS (
P_DEPARTMENT_ID IN NUMBER,
P_JOB_ID IN NUMBER,
P_AMOUNT IN NUMBER
) AS
LV_UPDATED_COUNT NUMBER := 0;
BEGIN
UPDATE YOUR_TABLE
SET
SALARY = SALARY + AMOUNT
WHERE DEPARTMENT_ID = P_DEPARTMENT_ID
AND JOB_ID = P_JOB_ID;
LV_UPDATED_COUNT := SQL%ROWCOUNT;
IF LV_UPDATED_COUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE('no records found');
ELSE
DBMS_OUTPUT.PUT_LINE('Number of records updated: ' || LV_UPDATED_COUNT);
END IF;
END SP_EMP_BONUS;
/
Hope this will answer your question:
create or replace procedure sp_emp_bonus(
ip_dep_id in departments.department_id%type,
ip_job_id in jobs.job_id%type,
amount in number
)
is
no_emp_found exception;
pragma exception_init(no_emp_found, -20101);
cnt integer := 0;
begin
update
employees e
set e.salary = e.salary + nvl(amount, 0)
where
e.department_id = ip_dep_id and
e.job_id = ip_job_id;
cnt := sql%rowcount;
if cnt = 0 then
raise no_emp_found;
else
dbms_output.put_line('Employees updated ' || cnt);
end if;
commit;
exception
when no_emp_found then
rollback;
dbms_output.put_line('No Employees found for given Department and Job');
when OTHERS then
rollback;
DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM,1,100));
end;
Thanks.

BULK COLLECT in a trigger

I need to create a trigger that updates a column in a table based on a transaction that happens in another table. Is there a way to use BULK COLLECT on joining two tables?
I need to collect data multiple times, is it possible to use BULK COLLECT inside another BULK COLLECT?
HERE is my existing trigger
create or replace trigger trans_hist_trg
AFTER INSERT OR UPDATE OF reason ON transaction_history
FOR EACH ROW
DECLARE
v_exists VARCHAR2(1);
v_valid code.valid_code%TYPE;
v_person_id person.id%TYPE;
TYPE Anumber_Type is TABLE of person.registration_number%TYPE INDEX BY binary_INTEGER;
v_NumberList Anumber_Type;
v_primaryAnumber person.primary_number%TYPE;
v_secondaryAnumber consolidated_numbers.secondary_number%TYPE;
v_anumber person.registration_number%TYPE;
BEGIN
IF(INSERTING) THEN
v_person_id := :NEW.person_id;
ELSE
v_person_id := :OLD.person_id;
END IF;
BEGIN
SELECT p.registration_number, p.primary_number, c.secondary_number INTO v_anumber, v_primaryAnumber, v_secondaryAnumber
FROM person p
LEFT JOIN consolidated_numbers c ON p.id = c.person_id WHERE p.id = v_person_id;
END;
BEGIN
SELECT women_act INTO v_exists
FROM person
WHERE id = v_person_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_exists := NULL;
END;
IF v_exists IS NULL AND :NEW.type_id IN (10,20,30,40,50) THEN
IF :NEW.reason NOT IN ('A1','B1') OR (:NEW.reason IN ('A1','B1') AND :NEW.action_date >= '01-JAN-00') THEN
BEGIN
SELECT valid_code INTO v_valid
FROM code
WHERE valid_code = :NEW.reason;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_exists := null;
END;
IF v_valid IS NOT NULL THEN
SELECT CASE
WHEN EXISTS (SELECT 1 FROM code WHERE valid_code = v_valid)
THEN 'Y' ELSE 'N' END INTO v_exists FROM dual;
END IF;
IF v_exists = 'Y' THEN
select registration_number BULK COLLECT into v_NumberList
FROM person where (registration_number=v_primaryAnumber OR
registration_number=v_anumber OR
registration_number=v_secondaryAnumber OR
primary_number= v_secondaryAnumber OR
primary_number=v_anumber OR
primary_number=v_primaryAnumber ) and (primary_number IS NOT NULL or primary_number <>'000000');
ELSE
select registration_number BULK COLLECT into v_NumberList
FROM person where (registration_number=v_primaryAnumber OR
registration_number=v_anumber OR
registration_number=v_secondaryAnumber OR
primary_number=v_anumber OR
primary_number=v_secondaryAnumber OR
primary_number=v_primaryAnumber OR
primary_number IS NULL);
END IF;
FOR indx IN 1 .. v_NumberList.COUNT
LOOP
update person set women_act = 'X'
where registration_number=v_NumberList(indx) and (women_act<>'X' or women_act IS NULL);
END LOOP;
update person set women_act = 'X'
where registration_number=v_anumber and (women_act<>'X' or women_act IS NULL);
END IF;
END IF;
mmm
END trans_hist_trg;
I need to make this block of code to be my main outer loop to iterate through all the numbers. But I'm unsure how. Please help.
SELECT p.registration_number, p.primary_number, c.secondary_number INTO v_anumber, v_primaryAnumber, v_secondaryAnumber
FROM person p
LEFT JOIN consolidated_numbers c ON p.id = c.person_id WHERE p.id = v_person_id;
Thank you!

How do you call a Procedure in Block SQL

My procedure is below;
I have to create an anonymous block that CALLS the procedure and DISPLAYS all the customers total price and number of cars purchased from that PARTICULAR city
Can someone show me how to do this BLOCK
My procedure is as FOLLOW:
CREATE OR REPLACE PROCEDURE getCars
( v_custname OUT car.custname%TYPE,
v_purchcost OUT car.purchcost%TYPE,
v_city IN customer.custcity%TYPE,
v_count OUT NUMBER,
v_total OUT car.purchcost%TYPE
)
AS
BEGIN
Select c.custname, Count(c.custname), SUM(purchcost)
INTO v_custname, v_count, v_total
From car c
JOIN customer cs
ON c.custname = cs.custname
WHERE cs.custcity = v_city
Group By c.custname;
END;
/
This is what I have for the BLOCK to call procedure BUT ITS NOT WORKING
SET SERVEROUTPUT ON
SET VER OFF
ACCEPT p_city PROMPT 'Enter City Name: ';
DECLARE
CURSOR cname IS
Select customer.custname
INTO custname
From customer
Where UPPER(custcity) = UPPER('&p_city');
BEGIN
FOR
v_car in cname
LOOP
getCars(v_car.custname, v_count, v_total);
END LOOP;
DBMS_OUTPUT.PUT_LINE(v_car.custname||v_count||v_total);
END;
/
If you only want to display them the following can be done:
CREATE OR REPLACE PROCEDURE getcars (
v_city IN customer.custcity%TYPE
) AS
BEGIN
FOR c IN (
SELECT
car.custname,
COUNT(car.custname) count_custname,
SUM(purchcost) purchcost
FROM
car
JOIN customer cs ON car.custname = cs.custname
WHERE
cs.custcity = v_city
GROUP BY
car.custname
) LOOP
dbms_output.put_line('Customer '
|| c.custname
|| ' has bought '
|| c.count_custname
|| ' totaling '
|| purchcost);
END LOOP;
END;
Call the procedure:
DECLARE
v_city customer.custcity%TYPE := 'some city';
BEGIN
getcars(v_city);
END;
Otherwise if you need to return it for each customer then you should use cursors or more complicated data structures.

ERROR PLS-00103: Encountered the symbol "DECLARE"?

I have a procedure that is bringing up the error 'PL/SQL: Statement ignored'. This message is a little vague and I cant figure out why my procedure won't compile. All the procedure should do is check if the customer's delivery date is less then the SYSDATE and if it is delete and if not print 'customer can't be deleted'.
The code for the procedure is here:
CREATE PROCEDURE remove_customer (customer_id VARCHAR2) IS
declare
ordersCount pls_integer;
BEGIN
select count(*) into ordersCount
from placed_orders
where fk1_customer_id = remove_customer.customer_id
and delivery_date < sysdate;
if ordersCount = 0 then
THEN
DELETE FROM order_line
WHERE order_line.FK1_order_id in
(SELECT order_id FROM placed_order
WHERE placed_order.FK1_customer_id = remove_customer.customer_id
);
DELETE FROM placed_order
WHERE placed_order.FK1_customer_id = remove_customer.customer_id;
DELETE FROM customer
WHERE customer.customer_id = remove_customer.customer_id;
total_customers := total_customers - 1;
ELSE
DBMS_OUTPUT.PUT_LINE 'Customer currently has a order been delivered';
END IF;
END;
And the error message is specifying PLS-00103: Encountered the symbol "DECLARE"
thanks for any advice.
This line:
IF placed_order.delivery_date < SYSDATE
doesn't make much sense - you cannot use a column like this (think about it: which of the rows in placed_order should be compared to SYSDATE? One? All?).
If you want to check whether this customer has a delivery that's already been delivered, you need an additional SELECT:
CREATE PROCEDURE remove_customer (customer_id VARCHAR2) IS
ordersCount pls_integer;
begin
select count(*) into ordersCount
from placed_orders
where fk1_customer_id = remove_customer.customer_id
and delivery_date < sysdate;
if ordersCount = 0 then
-- your code for deleting the customer here
else
-- raise error, show message, ...
end if;

How to check parameter value existence in a procedure?

The following is a procedure that uses 2 parameters: customer_code and pay_amount.
The procedure works as expected, however when I enter wrong cus_code I get the error istead of my custom checking:
ORA-01403: no data found
ORA-06512: at "XXXXX.CUST_PAY", line 19
ORA-06512: at line 2
The procedure:
CREATE OR REPLACE PROCEDURE cust_pay (temp_ccode IN NUMBER, pay_amount IN NUMBER)AS
BEGIN
DECLARE
Value_check NUMBER;
cbalance NUMBER;
BEGIN
SELECT Count(cus_code)
INTO value_check
FROM customer
WHERE cus_code = temp_ccode;
IF value_check IS NULL THEN
Dbms_Output.put_line('the value was not FOUND');
ELSE
UPDATE customer
SET cus_balance = cus_balance - pay_amount
WHERE cus_code = temp_ccode;
SELECT cus_balance
INTO cbalance
FROM customer
WHERE cus_code = temp_ccode;
IF cbalance < 0 THEN
Dbms_Output.put_line('The client owes us ' || cbalance);
ELSE
Dbms_Output.put_line('Customer new balance is ' || cbalance);
END IF;
END IF;
END;
END;
What do I do wrong? I guest I need to check the value before I make SELECT request, right?
Your problem is actually caused by your check:
SELECT COUNT (cus_code)
INTO value_check
FROM customer
WHERE cus_code = temp_ccode;
The above query will never return NULL, which is what you are checking for. If no values in the table match the parameter temp_ccode then the value_check will be 0. This in turn means your IF statement is incorrect and this causes your error later in your code.
There's a simpler and more efficient way of doing this though. You can use SQL%ROWCOUNT to find out how many rows were effected by your UPDATE. If the return value is 0 then your customer doesn't exist.
create or replace procedure cust_pay (
Ptemp_ccode in number
, Ppay_amount in number
) is
l_balance number;
begin
update customer
set cus_balance = cus_balance - Ppay_amount
where cus_code = Ptemp_ccode;
if SQL%ROWCOUNT = 0 then
dbms_output.put_line('The customer was not found.');
else
select cus_balance
into l_balance
from customer
where cus_code = temp_ccode;
if l_balance < 0 then
dbms_output.put_line('the client owes us ' || l_balance);
else
dbms_output.put_line('customer new balance is ' || l_balance);
end if;
end if;
end;
There's no need to handle the NO_DATA_FOUND exception in the select... into ... here as you've already guaranteed that the cus_code exists by your UPDATE statement.
Please note the other changes I've made:
Different naming conventions for parameters and variables so it's clear in the code which is which.
Removal of the additional nested PL/SQL block, which was unnecessary.
Generally speaking you should never use dbms_ouput.put_line in a PL/SQL block as you have to be there to see what's happening. It's fine for debugging processes but is fairly useless in production code.
It's also possible to use the RETURN statement to avoid the nested IF statements, which I think makes the code cleaner and easier to read; though this is a judgement call.
create or replace procedure cust_pay (
Ptemp_ccode in number
, Ppay_amount in number
) is
l_balance number;
begin
update customer
set cus_balance = cus_balance - Ppay_amount
where cus_code = Ptemp_ccode;
if SQL%ROWCOUNT = 0 then
return;
end if;
select cus_balance
into l_balance
from customer
where cus_code = temp_ccode;
if l_balance < 0 then
dbms_output.put_line('The client owes us ' || l_balance);
else
dbms_output.put_line('New balance is ' || l_balance);
end if;
end;
All of this assumes that your CUSTOMER table is unique on cus_code.
Corrected version of your code is here:
CREATE OR REPLACE PROCEDURE cust_pay (temp_ccode IN NUMBER, pay_amount IN NUMBER) AS
Value_check NUMBER;
cbalance NUMBER;
BEGIN
SELECT Count(cus_code)
INTO value_check
FROM customer
WHERE cus_code = temp_ccode;
IF value_check = 0 THEN
Dbms_Output.put_line('the value was not FOUND');
ELSE
UPDATE customer
SET cus_balance = cus_balance - pay_amount
WHERE cus_code = temp_ccode
RETURNING cus_balance
INTO cbalance;
IF cbalance < 0 THEN
Dbms_Output.put_line('The client owes us ' || -cbalance);
ELSE
Dbms_Output.put_line('Customer new balance is ' || cbalance);
END IF;
END IF;
END;
Egor Skriptunoff has just posted an intriguing answer and I don't want to claim any credit for the idea so I'm posting this as a different answer. It's actually possible to do everything in a single statement.
Based on the principle advocated in my other answer that it doesn't matter if you update the balance of a non-existent customer you can combine my own and Egor's answers to come up with this:
create or replace procedure cust_pay (
Ptemp_ccode in number
, Ppay_amount in number
) is
l_balance number;
begin
update customer
set cus_balance = cus_balance - Ppay_amount
where cus_code = Ptemp_ccode
returning cus_balance
into l_balance;
if SQL%ROWCOUNT = 0 then
dbms_output.put_line('The customer was not found.');
elsif l_balance < 0 then
dbms_output.put_line('The client owes us ' || l_balance);
else
dbms_output.put_line('New balance is ' || l_balance);
end if;
end;
The benefit of doing everything in a single statement is an increase in speed. Once again you don't need to worry about the NO_DATA_FOUND exception as you're doing an update only.
Everything I said in my previous answer still holds true, count(*) in a select... into... will never return null, always a number.
You could have a check before the sql query to verify parameter values
Another approach is to use EXCEPTION NO_DATA_FOUND
CREATE OR REPLACE PROCEDURE cust_pay (temp_ccode IN NUMBER,
pay_amount IN NUMBER,errcode OUT NUMBER)
AS
BEGIN
DECLARE
Value_check NUMBER;
cbalance NUMBER;
BEGIN
SELECT COUNT (cus_code)
INTO value_check
FROM customer
WHERE cus_code = temp_ccode;
IF value_check IS NULL
THEN
DBMS_OUTPUT.put_line ('the value was not FOUND');
ELSE
UPDATE customer
SET cus_balance = cus_balance - pay_amount
WHERE cus_code = temp_ccode;
SELECT cus_balance
INTO cbalance
FROM customer
WHERE cus_code = temp_ccode;
IF cbalance < 0
THEN
DBMS_OUTPUT.put_line ('The client owes us ' || cbalance);
ELSE
DBMS_OUTPUT.put_line ('Customer new balance is ' || cbalance);
END IF;
END IF;
END;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.put_line ('no data***' || SQLERRM);
errcode := 1;
END;