Error when calling PL/SQL function - sql

I have created a PL/SQL function that is supposed to be adding a given number of records to a table with the help of new_dept_new sequence that I've created earlier. The function compiled successfully. But I'm getting an error when calling the function:
BEGIN
dbms_output.put_line(myfunction3(3, 'OPERATIONS', 'NEW YORK'));
END;
Error report -
ORA-06503: PL/SQL: Function returned without value
The function:
CREATE OR REPLACE FUNCTION myfunction3(
records_count_in IN NUMBER, dept_name_in IN dept.dname%TYPE, loc_name_in IN dept.loc%TYPE)
RETURN INTERVAL DAY TO SECOND
IS
thestart TIMESTAMP := CURRENT_TIMESTAMP;
stopwatch INTERVAL DAY TO SECOND;
records_count NUMBER := records_count_in;
dept_name dept.dname%TYPE := dept_name_in;
loc_name dept.loc%TYPE := loc_name_in;
BEGIN
FOR i IN 1..records_count LOOP
INSERT INTO dept VALUES(new_dept_new.nextval, dept_name || TO_CHAR(new_dept_new.currval), loc_name || TO_CHAR(new_dept_new.currval));
COMMIT;
END LOOP;
stopwatch := CURRENT_TIMESTAMP - thestart;
DBMS_OUTPUT.PUT_LINE('Time for processing function: ');
RETURN stopwatch;
EXCEPTION WHEN OTHERS
THEN dbms_output.put_line('ERROR Processing Request for Department: ' || dept_name_in);
END;
Why is the function returned without value?
EDIT:
After receiving feedback from the comments I've edited the function and now the values are inserted into the table and the timestamp value is returned. I also want an application error to be raised if 0 rows were inserted.
So I did the following:
CREATE OR REPLACE FUNCTION myfunction3(
records_count_in IN NUMBER, dept_name_in IN dept.dname%TYPE, loc_name_in IN dept.loc%TYPE)
RETURN INTERVAL DAY TO SECOND
IS
thestart TIMESTAMP := CURRENT_TIMESTAMP;
stopwatch INTERVAL DAY TO SECOND;
records_count NUMBER := records_count_in;
dept_name dept.dname%TYPE := dept_name_in;
loc_name dept.loc%TYPE := loc_name_in;
BEGIN
FOR i IN 1..records_count LOOP
INSERT INTO dept (deptno, dname, loc)
VALUES(new_dept_new.nextval, dept_name || TO_CHAR(new_dept_new.currval), loc_name || TO_CHAR(new_dept_new.currval));
IF SQL%ROWCOUNT = 0 THEN
raise_application_error (-20001, 'ERROR Processing Request for Department: ' || dept_name_in);
END IF;
END LOOP;
stopwatch := CURRENT_TIMESTAMP - thestart;
DBMS_OUTPUT.PUT_LINE('Time for processing function: ');
RETURN stopwatch;
END;
However if I'm calling the function like this and zero records are inserted, I'm not getting my custom error message.
BEGIN dbms_output.put_line(myfunction3(0, 'DEV_OPS', 'NEW YORK')); END;
How can I fix this?

Nothing seems to be wrong with your functionexcept for the exception block.I was able to simulate the scenario with the table and column names you used and it worked perfectly fine. If there are other issues like missing table,sequence it would not have compiled for you. So, the only reason for it to fail is your insert statement. It could be either constraint violation, length of value exceeds the column length limit etc.
You can make a slight change in your exception block by adding a raise statement.This will help you to identify the actual error in your calling program.
EXCEPTION WHEN OTHERS
THEN dbms_output.put_line('ERROR Processing Request for Department: ' || dept_name_in);
RAISE;
Additionally, Never run insert statements like
INSERT INTO dept VALUES (...)
It is not a good coding practice and difficult to know which order it is going to insert especially when there are many columns.
Always mention the columns explicitly
INSERT INTO dept ( dept_id,dname,loc) VALUES (..,..,..);

There is some exception in your function that causes your function to hit the exception block. There is no return statement in your exception block and hence the error. Add a RAISE statement to see what's causing the exception.

Related

How can I handle this compilation error through exception?

How can I handle this compilation error through exception?
declare
table_or_view_does_not_exist exception;
pragma exception_init(table_or_view_does_not_exist,-00942);
b exception;
pragma exception_init(b,-00942);
d_table varchar2(200);
c_table varchar2(200);
c_count Number;
begin
begin
d_table:='drop table audit_table PURGE';
execute immediate d_table;
exception
when table_or_view_does_not_exist then
null;
end;
<<lable>>
c_table := 'create table audit_table
(table_name varchar2(50),
column_name varchar2(50),
count_type varchar2(50),
v_count number)';
execute immediate c_table;
select count(*) into c_count from customer_profile where cust_id is null;
insert into audit_table columns (table_name,column_name,count_type,v_count) values('customer_profile','cust_id','null','c_count');
exception
when b then
GOTO lable;
end;
Error report:
ORA-06550: line 25, column 13:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 25, column 1:
PL/SQL: SQL Statement ignored
ORA-06550: line 28, column 2:
PLS-00375: illegal GOTO statement; this GOTO cannot branch to label 'LABLE'
ORA-06550: line 28, column 2:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
What you do, is just bad practice. In Oracle, we don't create tables in PL/SQL but at SQL level and then use them in our procedures.
In your case, you'd
-- create table first
create table audit_table ...;
-- use it in PL/SQL procedure
declare
...
begin
...
insert into audit_table ...
end;
/
You can't "handle" compilation error through exception. What you could do is to put insert statement into dynamic SQL. Also, it wouldn't harm if you used valid syntax (there's no columns "keyword" there).
execute immediate q'[insert into audit_table
(table_name, column_name, count_type, v_count)
values('customer_profile', 'cust_id', 'null', :a)]'
using c_count;
but - once again - that's just bad practice. Don't do it that way, there's no benefit and many disadvantages.
As of goto - well, jumping around your code is almost always wrong. Error you got says that you can't jump out of the exception handler so your idea was wrong anyway.
You actually can trap compilation errors, by wrapping the whole block in an execute immediate so compilation is postponed until runtime. But that's not the right way to go.
Your real issue is that you are referring to the object you are dynamically dropping/creating as a hard-coded dependency of your PL/SQL block:
insert into audit_table ... -- this is a hard dependency
You can't compile PL/SQL if hard dependencies don't exist until run time. If you really must dynamically drop/recreate the table (and I agree with the above reviewer that this is architecturally questionable), you must also make this insert dynamic as well in order to break the dependency chain:
execute immediate 'insert into audit_table values (:col1, :col2, :col3, :col4)' using 'customer_profile','cust_id','null','c_count'; -- this creates no dependency
That will prevent Oracle from creating an object dependency on this table so your code can compile even though the table does not exist at compile time.
But once again, please be sure this is the right technique. There are only a few rare cases where this actually makes good architectural sense. Oh, and please remove the GOTO and label junk. We really don't use that in modern languages; they create unmanageable spaghetti code.
Could you consider another way to control the flow - comments in the code:
DECLARE
cmd VarChar2(200);
c_count Number(6);
--
Status VarChar2(255);
BEGIN
cmd :='drop table audit_table';
Begin -- 1st nested PL/SQL block
Execute Immediate cmd;
Status := 'OK';
Exception -- this could be raised if the table doesn't exist which is probably OK
When OTHERS Then
Status := 'OK'; -- SQLERRM = ORA-00942: table or view does not exist - nothing happens - it would be droped anyway
-- Status := 'ERR - ' || SQLERRM; -- this is alternative if you want to do something else with this ERR
End;
If Status != 'OK' Then -- here you can check the status and decide what to do
GoTo EndIt;
End If;
--
<<lable>> -- in this code there is no need for this label
cmd := 'create table audit_table (table_name varchar2(50), column_name varchar2(50), count_type varchar2(50), v_count number)';
Begin -- 2nd nested PL/SQL block
Execute Immediate cmd;
Status := 'OK';
Exception
When OTHERS Then
Status := 'ERR - ' || SQLERRM;
End;
If Status != 'OK' Then -- here you can check the status and decide what to do
dbms_output.putline(Status); -- send error message and exit - there is no audit_table
GoTo EndIt;
Else
Null; -- here you can do something else (if maybe the table already exists)
End If;
--
-- think about what could go wrong below (2 lines) and either leave it as it is or put it in the 3rd nested PL/SQL block
Select count(*) Into c_count From customer_profile Where cust_id Is Null;
insert into audit_table (table_name, column_name, count_type, v_count) values('customer_profile', 'cust_id', 'Null', c_count);
<<EndIt>> -- used to end the block if needed
Null;
EXCEPTION
When OTHERS Then
dbms_output.putline(SQLERRM); -- You can not get out of here since it is main PL/SQL blok that went into an exception
END;
Addition
Below is an option of basic steps to do the job (3rd block) without compilation errors. I have no possibility to check it out so please do it yourself (there could be some syntax problem or something else). The goal is to be sure that you can insert the record...
I declared sq variable to construct insert command and the end of the code above could be (please check the command yourself - I can't test it right now) something like here:
DECLARE
...
sq VarChar2(1) := ''''; -- this is single-quote character
...
BEGIN
...
...
--
-- think about what could go wrong below (2 lines) and either leave it as it is or put it in the 3rd nested PL/SQL block
Select count(*) Into c_count From customer_profile Where cust_id Is Null;
cmd := 'insert into audit_table (table_name, column_name, count_type, v_count) values(' || sq || 'customer_profile' || sq || ', ' || sq || 'cust_id' || sq || ', ' || sq || 'Null' || sq || ', ' || c_count || ')';
Begin -- 3rd nested PL/SQL block
Select Count(*) Into c_count From all_tables Where table_name = 'audit_table' and owner = 'your_table_owner'; -- check if table exist - if it doesn't set Status
If c_count = 1 Then -- if table exists then you can insert the record
Execute Immediate cmd;
Status := 'OK'; -- all done
Else
Status := 'ERR';
End If;
Exception
When OTHERS Then
Status := 'ERR - ' || SQLERRM; -- catch error and pass it to the main block
End;
If Status != 'OK' Then -- here you can check the status and decide what to do
dbms_output.putline(Status); -- send error message and exit - there is no audit_table -->> OR DO SOMETHING ELSE
GoTo EndIt;
Else
Null; -- here you can do something else if you wish
End If;
--
<<EndIt>> -- used to end the block if needed
Null;
EXCEPTION
When OTHERS Then
dbms_output.putline(SQLERRM); -- You can not get out of here since it is main PL/SQL blok that went into an exception
END;

if-else condition not working correctly in PL/SQL

I am writing a procedure to delete department from a table. It takes depatment id as argument and delete the department with given id. but it is not working correctly.When i didnot use EXCEPTION, it only give output when gien department id is present in table but if the id is not present in table it throw error. When i use exception, It did not check the if else condition.
Here is my procedure
CREATE OR REPLACE PROCEDURE del_job(j_id number) IS
jj_id bb_department.iddepartment%type;
BEGIN
SELECT IDDEPARTMENT
INTO jj_id
FROM BB_DEPARTMENT
WHERE IDDEPARTMENT=j_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
jj_id := NULL;
IF (jj_id=j_id) THEN
DELETE FROM BB_DEPARTMENT
WHERE IDDEPARTMENT=j_id;
dbms_output.put_line ('Job Deleted');
ELSIF(jj_id=0) THEN
dbms_output.put_line ('No Job Deleted');
END IF;
END;
/
Your indentation seems to imply that you want your if statement to be part of the normal flow rather than part of the exception block. But your actual code has the if statement in the exception handler. Since you're assigning a null to jj_id in the exception handler before running the if statement and null is never equal to nor unequal to any value, neither your if nor your elsif clause can ever be true so neither dbms_output call will be made.
Assuming your indentation shows your actual intent, my guess is that you want a nested PL/SQL block for the select statement and exception handler.
CREATE OR REPLACE PROCEDURE del_job(j_id number) IS
jj_id bb_department.iddepartment%type;
BEGIN
BEGIN
SELECT IDDEPARTMENT
INTO jj_id
FROM BB_DEPARTMENT
WHERE IDDEPARTMENT=j_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
jj_id := NULL;
END;
IF (jj_id=j_id) THEN
DELETE FROM BB_DEPARTMENT
WHERE IDDEPARTMENT=j_id;
dbms_output.put_line ('Job Deleted');
ELSIF(jj_id=0) THEN
dbms_output.put_line ('No Job Deleted');
END IF;
END;
/
You are making things overly difficult. DELETE sets sql%rowcount to the number of rows processed. So there is no need to select the department; just delete the appropriate id. If you want a confirmation message then test sql%rowcount. If the row was deleted it will contain 1 (or greater), if the id did not exist it will contain 0. Print the appropriate message.
create or replace procedure del_job(j_id number) is
begin
delete
from bb_department
where iddepartment=j_id;
if sql%rowcount > 0 then
dbms_output.put_line ('Job Deleted');
else
dbms_output.put_line ('No Job Deleted');
end if;
end del_job;
/

How to find what data caused Oracle function failure?

sorry if this not the right place.
I am doing a SQL SELECT statement, invoking a function. It's a large data dump - about 10,000 records.
I am calling a function to preform some calculations, but its failing.
One ore more of those records has bad data that is causing the function to crash.
Is there any way to see exactly what data caused the crash readily Or should I create some code to run the function by hand for each of 10,000 records? I could create code that generates the input data fairly straightforwardly, then run the function like this SELECT MY_FUNCT(1,1,1) FROM DUAL; but I am wondering if there is a better way.
For reference I am running the SQL query like this.
SELECT
MY_FUNCT(A.FOO, A.BAR)
FROM TABLE A
WHERE ....;
As others have said, you just need to handle the error and not raise it all the way. A neat way of doing this would be to create a wrapper function for your function that sometimes fails, you can declare this function within your select query using a with pl/sql clause:
Let's say this is your function that sometimes fails
create or replace function my_funct (inputnumber number)
return varchar2
is
sString varchar2(200);
begin
if inputnumber = 42 then
raise_application_error(-20001,'UH OH!');
end if;
sString := 'Input: '||inputnumber;
return sString;
end my_funct;
/
We can define a function that takes the same inputs, and just calls this function, then we just need to add some error handling (obviously never just rely on dbms_output to capture errors, this is just to make it obvious):
function my_funct_handle (inputnumber number)
return varchar2
is
begin
return my_funct (inputnumber => inputnumber);
exception when others then
dbms_output.put_line(sqlerrm||' at '||inputnumber);
return 'ERROR';
end;
And we can then just stick that in our query using with function
with
function my_funct_handler (inputnumber number)
return varchar2
is
begin
return my_funct (inputnumber => inputnumber);
exception when others then
dbms_output.put_line(sqlerrm||' at '||inputnumber);
return 'ERROR';
end;
select my_funct_handler (id), string_col
from as_table;
/
I get both the dbms_output text to describe the error and the ID but I could also filter on the results of that function to only show me the erroring rows:
with
function my_funct_handle (inputnumber number)
return varchar2
is
begin
return my_funct (inputnumber => inputnumber);
exception when others then
dbms_output.put_line(sqlerrm||' at '||inputnumber);
return 'ERROR';
end;
select my_funct_handle (id), string_col
from as_table
where my_funct_handle (id) = 'ERROR';
/
MY_FUNCT_HANDLE(ID)
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
STRI
----
ERROR
blah
ORA-20001: UH OH! at 42
ORA-20001: UH OH! at 42
(I get two errors shown in the dbms_output output as the function can be called multiple times - once as part of the select list and once as part of the where clause.)
One option is to handle the exception properly, e.g.
create or replace function my_funct(par_foo in number, par_bar in number)
return number
is
retval number;
begin
select sal
into retval
from emp
where ename = par_foo
and deptno = par_bar;
return par_foo/par_bar;
exception --> this
when no_data_found then
return null;
end;
If you want, you can even log those errors. How? Make the function autonomous transaction (so that it could write into the log table; you'll have to commit that insert). Store all relevant information (including SQLERRM). Once your code finishes, check what's written in the log file and then decide what to do.
Or, you could even continue current task by enclosing that select into its own begin-exception-end block within a loop, e.g.
begin
for cur_r in (select ... from ...) loop
begin
-- your current SELECT
SELECT
MY_FUNCT(A.FOO, A.BAR)
FROM TABLE A
WHERE ....;
exception
when others then
dbms_output.put_line(cur_r.some_value ||': '|| sqlerrm);
end;
end loop;
end;
one better approach is to create a error handler Package/Procedure which will write it into a table and call it from the function, this way all the errors will be captured in a Oracle table.
--- untested -- You can create other columns to capture the function name, date, and other details in the error table.
PROCEDURE SP_ERROR_INS_ERR_COMMON (n_ERR_CODE NUMBER, c_ERR_SOURCE VARCHAR2, n_ERR_LINE NUMBER, c_ERR_DESC VARCHAR2, C_USER_COMMENT VARCHAR2 ) IS
n_Log_Id NUMBER;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
n_Log_Id := 0;
INSERT INTO ERROR_LOG_COMMON
(ERROR_CODE, ERROR_SOURCE, ERROR_LINE, ERROR_DESCRIPTION, USER_COMMENT)
VALUES
(n_ERR_CODE, c_ERR_SOURCE, n_ERR_LINE, c_ERR_DESC, C_USER_COMMENT
);
COMMIT;
raise_application_error( -20001,SUBSTR(SQLERRM, 1, 200));
END SP_ERROR_INS_ERR_COMMON;
In your function you can call the error
EXCEPTION
WHEN OTHERS
THEN
vn_errcode := SQLCODE;
vc_errmsg := SUBSTR (SQLERRM, 1, 4000);
sp_error_ins_err_common(vn_ErrCode,
'SP_RP_DIM_COMPARE', NULL, vc_ErrMsg, substr('batch_id ' || g_BATCH_ID ||
l_str_err_msg,1,4000) );
RAISE;

SQL How to handle an exception in user defined functions?

CREATE OR REPLACE Function fun_Find_Staff_Name( v_staffid IN NUMBER )
RETURN VARCHAR2
IS
staff_name VARCHAR2(60);
CURSOR c_staff IS
SELECT staff_firstName || ' ' || staff_lastName
into staff_name
FROM staff
WHERE staff_id = v_staffid;
BEGIN
OPEN c_staff;
FETCH c_staff INTO staff_name;
CLOSE c_staff;
RETURN staff_name;
END;
This function takes a staff_id and returns the corresponding staff name. I want to handle the exception if the user enters an ID that is invalid and display an error message? Where do I add the exception block?
SET serveroutput ON;
DECLARE
v_staff_ID NUMBER := &StaffID;
v_message VARCHAR2(100);
BEGIN
v_message := fun_Find_Staff_Name(v_staff_ID);
dbms_output.put_line(v_message);
END;
I used this anonyomus block to check if the function works properly.
In my opinion you don't need an explicit cursor, an embedded sql will do the trick:
CREATE OR REPLACE Function fun_Find_Staff_Name
( v_staffid IN NUMBER )
RETURN VARCHAR2
IS
staff_name VARCHAR2(60);
BEGIN
SELECT staff_firstName || ' ' || staff_lastName
INTO staff_name
FROM staff WHERE staff_id = v_staffid;
RETURN staff_name;
EXCEPTION
WHEN NO_DATA_FOUND THEN
--in case of an invalid id no data will be found
--you can treat your exception here
--example
DBMS_OUTPUT.PUT_LINE('No data found for this id: ' || v_staffid);
RAISE;
WHEN OTHERS THEN
RAISE;
END fun_Find_Staff_Name ;
And please remember that when you are handling exceptions don't forget to raise it; if not the database will not rollback the transaction in case of an error.
If it yells some errors at the compilation, I am sorry, but I wrote the code without compiling it (in a hurry from my mob).
Cheers and happy coding.

Why is the exception NO_DATA_FOUND not being triggered?

So the problem i am having is that if i execute the following procedure and the cursor doesnt find the parameter being passed, it continues to execute the block (insert statement) but instead of throwing the NO_DATA_FOUND exception error it throws a parent/foreign key error.
CREATE OR REPLACE PACKAGE ASSIGNMENT3 IS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE);
END ASSIGNMENT3;
/
CREATE OR REPLACE PACKAGE BODY ASSIGNMENT3 AS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE) IS
CURSOR ADCOST_CUR IS
SELECT ACTUALCOST
FROM ADVERTISEMENT
WHERE ADVERTISEMENT.CAMPAIGNTITLE = CTITLE;
V_TOTALCOST NUMBER;
BEGIN
V_TOTALCOST := 0;
FOR INVOICE_REC IN ADCOST_CUR
LOOP
V_TOTALCOST := V_TOTALCOST + INVOICE_REC.ACTUALCOST;
END LOOP;
INSERT INTO INVOICE(INVOICENO, CAMPAIGNTITLE, DATEISSUED, DATEPAID, BALANCEOWING, STATUS)
VALUES (AUTOINCREMENTINVOICE.nextval, CTITLE, SYSDATE, NULL,V_TOTALCOST,NULL);
EXCEPTION WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('ERROR:The campaign title you entered returned no record(s), please enter a valid campaign title.');
COMMIT;
END END_CAMPAIGN;
END ASSIGNMENT3;
/
SET SERVEROUTPUT ON
EXECUTE ASSIGNMENT3.END_CAMPAIGN('Panasonic 3D TV');
While the parent foreign key error is correct, i dont want the block to execeute if the cursor doesnt return a row. Why is this happening?
Also, in terms of placing the COMMIT, where exactly do i tell it to COMMIT? Before the exception or after?
This is for a uni assignment.
When you loop over a cursor like that, if the cursor finds no matching rows, the loop simply doesn't execute at all. A NO_DATA_FOUND exception would only be raised if you had a SELECT ... INTO ... statement inside the BEGIN/END block that did not return any rows.
Where you have the COMMIT placed now, it is part of the EXCEPTION block -- but your indentation implies that you want it to execute whether the exception occurred or not. In this case, I would just put the COMMIT immediately after the INSERT, since it only matters if the INSERT is successful.
"So is there no way to have the NODATAFOUND exception trigger when
using a cursor, if the CTITLE parameter isnt found in the table"
What you could do is test the value of V_TOTAL_COST. If it is zero raise an exception, like this:
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE) IS
CURSOR ADCOST_CUR IS
SELECT ACTUALCOST
FROM ADVERTISEMENT
WHERE ADVERTISEMENT.CAMPAIGNTITLE = CTITLE;
V_TOTALCOST NUMBER;
BEGIN
V_TOTALCOST := 0;
FOR INVOICE_REC IN ADCOST_CUR
LOOP
V_TOTALCOST := V_TOTALCOST + INVOICE_REC.ACTUALCOST;
END LOOP;
if v_total_cost = 0 then
raise no_data_found;
end if;
INSERT INTO INVOICE(INVOICENO, CAMPAIGNTITLE, DATEISSUED, DATEPAID, BALANCEOWING, STATUS)
VALUES (AUTOINCREMENTINVOICE.nextval, CTITLE, SYSDATE, NULL,V_TOTALCOST,NULL);
COMMIT;
EXCEPTION WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('ERROR:The campaign title you entered returned no record(s), please enter a valid campaign title.');
END END_CAMPAIGN;
This assumes you have a business rule that ACTUAL_COST cannot be zero.
Alternatively, there is the clunkier workaround of incrementing a counter in the loop and testing whether it is zero after the loop.
As for where to place the commit I would say the answer is not inside the procedure. The client (sqlplus in this case) should determine if the transaction will commit or rollback as the call to end the campaign may be just a part of a wider process. Also assuming that a campaign can exist without any advertisements then I would have an explicit check that the campaign title is valid perhaps against the table of CAMPAIGN? as suggested below:
CREATE OR REPLACE PACKAGE ASSIGNMENT3 IS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE);
END ASSIGNMENT3;
/
CREATE OR REPLACE PACKAGE BODY ASSIGNMENT3 AS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE) IS
V_VALID_CAMPAIGN INTEGER;
V_TOTALCOST NUMBER;
BEGIN
-- Check this campaign title is valid
/* Will get you NO_DATA_FOUND here if CTITLE is invalid so wrap in
another BEGIN END block to throw own custom error that the client
of this procedure can handle (if it wants) */
BEGIN
SELECT 1
INTO V_VALID_CAMPAIGN
FROM CAMPAIGN
WHERE CAMPAIGNTITLE = CTITLE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20000,'The campaign title you entered returned no record(s), please enter a valid campaign title.');
END;
-- Now tot up the cost of ads in this campaign and raise the invoice
SELECT SUM(ACTUALCOST)
INTO V_TOTALCOST
FROM ADVERTISEMENT
WHERE ADVERTISEMENT.CAMPAIGNTITLE = CTITLE;
INSERT INTO INVOICE(INVOICENO, CAMPAIGNTITLE, DATEISSUED, DATEPAID, BALANCEOWING, STATUS)
VALUES (AUTOINCREMENTINVOICE.nextval, CTITLE, SYSDATE, NULL,V_TOTALCOST,NULL);
END END_CAMPAIGN;
END ASSIGNMENT3;
/
EXECUTE ASSIGNMENT3.END_CAMPAIGN('Panasonic 3D TV');
COMMIT;