PL/SQL trigger: can't create apex user - sql

I am working on a database and one of the final tasks left is to create user accounts. For some reason I can't seem to get it to work. In fact, none of the commented code works when uncommented. Our primary concern is being able to automate the creation of user accounts rather than creating them manually. I am hoping someone can shed some light into the errors of my ways so my code will compile.
create or replace TRIGGER trg_Students
BEFORE INSERT OR UPDATE OF SRN, Surname, Forename, Username, DOB, Date_Cv_Submitted, Date_cv_approved, same_address, home_phone_no, home_postcode ON Students
FOR EACH ROW
BEGIN
IF INSERTING THEN
:NEW.SRN := seq_SRN.nextval;
CREATE USER :new.USERNAME
IDENTIFIED BY PASSWORD
PROFILE app_user
PASSWORD EXPIRE;
--IF (ACTIVE_ACCOUNT = 'Y' AND CV_APPROVED = NULL) THEN
-- RAISE_APPLICATION_ERROR(-20000, 'Cannot create an account that is active before the cv is approved!');
--END IF;
END IF;
--IF UPDATING THEN
--IF (DATE_CV_APPROVED != NULL) THEN
--:new.Active_Account := 'Y';
--END IF;
--END IF;
:NEW.forename := INITCAP(:NEW.forename);
:NEW.surname := INITCAP(:NEW.surname);
:NEW.home_postcode := UPPER(:NEW.home_postcode);
:NEW.home_phone_no := REGEXP_REPLACE(:NEW.home_phone_no, '[^[:digit:]]', '');
:NEW.home_phone_no := REGEXP_REPLACE(:NEW.home_phone_no,
'([[:digit:]]{5})([[:digit:]]{6})', '(\1) \2');
IF :NEW.same_address = 'Y' THEN
:NEW.term_no := :NEW.home_no;
:NEW.term_postcode := :NEW.home_postcode;
:NEW.term_phone_no := :NEW.home_phone_no;
ELSE
:NEW.term_postcode := UPPER(:NEW.term_postcode);
:NEW.term_phone_no := REGEXP_REPLACE(:NEW.term_phone_no, '[^[:digit:]]', '');
:NEW.term_phone_no := REGEXP_REPLACE(:NEW.term_phone_no,
'([[:digit:]]{5})([[:digit:]]{6})', '(\1) \2');
END IF;
IF (:NEW.DOB + NUMTOYMINTERVAL(18,'YEAR') > SYSDATE) THEN
RAISE_APPLICATION_ERROR(-20000, 'Client must be at least 18 years of age!');
END IF;
IF (:NEW.Date_cv_approved < :NEW.date_cv_submitted) THEN
RAISE_APPLICATION_ERROR(-20000, 'Cannot approve a cv before it is submitted!');
END IF;
END;
the error is
Compilation failed, line 6 (13:19:44) The line numbers associated with
compilation errors are relative to the first BEGIN statement. This
only affects the compilation of database triggers. PLS-00103:
Encountered the symbol "CREATE" when expecting one of the following: (
begin case declare else elsif end exit for goto if loop mod null
pragma raise return select update while with << continue
close current delete fetch lock insert open rollback savepoint set sql
execute commit forall merge pipe purge.
I have changed my method to:
APEX_UTIL.CREATE_USER(
p_user_name => :new.USERNAME,
P_web_password => 'Password123');
and it now produces this error:
An API call has been prohibited. Contact your administrator. Details
about this incident are available via debug id "46046".
Contact your application administrator.

Seems funny that i am answering my own question but i solved the issue. The code i used to create apex users is the following.
APEX_UTIL.CREATE_USER(
p_user_name => :new.USERNAME,
P_web_password => 'Password123',
p_change_password_on_first_use => 'Y');
The error above was solved by changing the security settings from within the application builder to allow the api to work this is found by the following.
Application Builder -> (Your Application) -> Shared Components -> Security Attributes and finally tick the boxes next to runtime API Usage at the bottom of the page, i ticked all 3 as i needed to.

You cannot execute create statements directly from PLSQL.
change this to:
IF INSERTING THEN
:NEW.SRN := seq_SRN.nextval;
execute immediate 'CREATE USER '||:new.USERNAME ||'
IDENTIFIED BY PASSWORD
PROFILE app_user
PASSWORD EXPIRE';

You have to set your trigger for an autonomous transaction in order to commit within a trigger.
CREATE TRIGGER your_trigger
BEFORE INSERT ON your_table FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
...
HTH

Related

Cannot rollback while a subtransaction is active - Error 2D000

I have written a stored procedure that basically loops over an array of fields and performs some manipulation in the db for each iteration. What I want to achieve is, either all the iterations of loops should occur or neither one of them should occur.
So let's say there were 5 elements in the fields array and the loop iterates up to the 3rd element before noticing that some condition is true and throwing an error, I want to rollback all the changes that occurred during the first 2 iterations. I've used ROLLBACK statements to achieve the same, but every time it reaches the ROLLBACK statement it throws the following error:
Cannot rollback while a subtransaction is active : 2D000
Surprisingly, it works as normal if I comment out the outobj := json_build_object('code',0); statement within the EXCEPTION WHEN OTHERS THEN block or if I remove that whole block completely.
I've checked the PostgreSQL documentation for error codes, but it didn't really help. My stored procedure is as follows:
CREATE OR REPLACE PROCEDURE public.usp_add_fields(
field_data json,
INOUT outobj json DEFAULT NULL::json)
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
v_user_id bigint;
farm_and_bussiness json;
_field_obj json;
_are_wells_inserted boolean;
BEGIN
-- get user id
v_user_id = ___uf_get_user_id(json_extract_path_text(field_data,'user_email'));
IF(v_user_id IS NULL) THEN
outobj := json_build_object('code',17);
RETURN;
END IF;
-- Loop over entities to create farms & businesses
FOR _field_obj IN SELECT * FROM json_array_elements(json_extract_path(field_data,'fields'))
LOOP
-- check if irrigation unit id is already linked to some other field
IF(SELECT EXISTS(
SELECT field_id FROM user_fields WHERE irrig_unit_id LIKE json_extract_path_text(_field_obj,'irrig_unit_id') AND deleted=FALSE
)) THEN
outobj := json_build_object('code',26);
-- Rollback any changes made by previous iterations of loop
ROLLBACK;
RETURN;
END IF;
-- check if this field name already exists
IF( SELECT EXISTS(
SELECT uf.field_id FROM user_fields uf
INNER JOIN user_farms ufa ON (ufa.farm_id=uf.user_farm_id AND ufa.deleted=FALSE)
INNER JOIN user_businesses ub ON (ub.business_id=ufa.user_business_id AND ub.deleted=FALSE)
INNER JOIN users u ON (ub.user_id = u.user_id AND u.deleted=FALSE)
WHERE u.user_id = v_user_id
AND uf.field_name LIKE json_extract_path_text(_field_obj,'field_name')
AND uf.deleted=FALSE
)) THEN
outobj := json_build_object('code', 22);
-- Rollback any changes made by previous iterations of loop
ROLLBACK;
RETURN;
END IF;
--create/update user business and farm and return farm_id
CALL usp_add_user_bussiness_and_farm(
json_build_object('user_email', json_extract_path_text(field_data,'user_email'),
'business_name', json_extract_path_text(_field_obj,'business_name'),
'farm_name', json_extract_path_text(_field_obj,'farm_name')
), farm_and_bussiness);
IF(json_extract_path_text(farm_and_bussiness, 'code')::int != 1) THEN
outobj := farm_and_bussiness;
-- Rollback any changes made by previous iterations of loop
ROLLBACK;
RETURN;
END IF;
-- insert into users fields
INSERT INTO user_fields (user_farm_id, irrig_unit_id, field_name, ground_water_percent, surface_water_percent)
SELECT json_extract_path_text(farm_and_bussiness,'farm_id')::bigint,
json_extract_path_text(_field_obj,'irrig_unit_id'),
json_extract_path_text(_field_obj,'field_name'),
json_extract_path_text(_field_obj,'groundWaterPercentage'):: int,
json_extract_path_text(_field_obj,'surfaceWaterPercentage'):: int;
-- add to user wells
CALL usp_insert_user_wells(json_extract_path(_field_obj,'well_data'), v_user_id, _are_wells_inserted);
END LOOP;
outobj := json_build_object('code',1);
RETURN;
EXCEPTION WHEN OTHERS THEN
raise notice '% : %', SQLERRM, SQLSTATE;
outobj := json_build_object('code',0);
RETURN;
END;
$BODY$;
If you have an EXCEPTION clause in a PL/pgSQL block, that whole block will be executed in a subtransaction that is rolled back when an exception happens. So you cannot use COMMIT or ROLLBACK in such a block.
If you really need that ROLLBACK, rewrite your code like this:
DECLARE
should_rollback boolean := FALSE;
BEGIN
FOR ... LOOP
BEGIN -- inner block for exception handling
/* do stuff */
IF (/* condition that should cause a rollback */) THEN
should_rollback := TRUE;
EXIT; -- from LOOP
END IF;
EXCEPTION
WHEN OTHERS THEN
/* handle the error */
END;
END LOOP;
IF should_rollback THEN
ROLLBACK;
/* do whatever else is needed */
END IF;
END;
Now the rollback does not happen in a block with an exception handler, and it should work the way you want.
Explanation:
Based on the clue provided by #Laurez Albe, I came up with a cleaner way to solve the above problem.
Basically, what I've done is, I've raised a custom exception whenever a condition is true. So when an exception is thrown, all the changes made by block X are rolled back gracefully. I can even perform last minute cleanup within the exception conditional blocks.
Implementation:
CREATE OR REPLACE procedure mProcedure(INOUT resp json DEFAULT NULL::JSON)
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
field_data json := '{ "fields": [1,2,3,4,5] }';
_field_id int;
BEGIN
-- Start of block X
FOR _field_id IN SELECT * FROM json_array_elements(json_extract_path(field_data,'fields'))
LOOP
INSERT INTO demo VALUES(_field_id);
IF(_field_id = 3) THEN
RAISE EXCEPTION USING ERRCODE='22013';
END IF;
IF(_field_id = 5) THEN
RAISE EXCEPTION USING ERRCODE='22014';
END IF;
END LOOP;
SELECT json_agg(row_to_json(d)) INTO resp FROM demo d;
RETURN;
-- end of block X
-- if an exception occurs in block X, then all the changes made within the block are rollback
-- and the control is passed on to the EXCEPTION WHEN OTHERS block.
EXCEPTION
WHEN sqlstate '22013' THEN
resp := json_build_object('code',26);
WHEN sqlstate '22014' THEN
resp := json_build_object('code',22);
END;
$BODY$;
Demo:
Dbfiddle

How to read all database entries?

This is the code I've got at the moment:
procedure TfrmLogin.performQuery;
begin
query.SQL.Text := 'SELECT * FROM LoginDatabase';
query.Open;
end;
procedure TfrmLogin.FormCreate(Sender: TObject);
begin
con := TFDConnection.Create(nil);
query := TFDQuery.Create(con);
con.LoginPrompt := false;
con.Open('DriverID=SQLite;Database=C:\Users\katiee\Documents\Embarcadero\' +
'Studio\Projects\ProgramDatabase;');
query.Connection := con;
performQuery;
username := query.FieldByName('Username').AsString;
passcode := query.FieldByName('Passcode').AsString;
end;
procedure TfrmLogin.btnLoginClick(Sender: TObject);
begin
if (edtUsername.Text = username) and (edtPasscode.Text = passcode) then
frmPayInfo.show
else
if MessageDlg('The username or password entered is incorrect.', mtWarning,
[mbOK, mbCancel], 0) = mrCancel then
frmLogin.Close;
end;
Right now the code, as it doesn't read all database entries, only allows the first database entry as correct answer.
I need to be able to enter any correct combination of username and pass-code from the database and open the form.
Why not call the .Locate method on the dataset? This way you will not be opening and closing the dataset, as you would need to with using a parameterised query.
Using the .Locate method will allow you to check (based off one, or many fields) if a certain record can be found.
If the record is found .Locate will return true.
For example.
if query.Locate('username;passcode', VarArrayOf([query.FieldByName('Username').AsString, query.FieldByName('Passcode').AsString]), [loPartialKey]) then
begin
frmPayInfo.show;
end
else
begin
Showmessage('User not found, or password incorrect');
end;
I haven't got Delphi on this machine, so syntax may not be 100%.

How to remove(delete) multiple users from different tables using PL/SQL

I wrote this query to delete one user from different tables using pl/sql.
Example: I run this query to delete one user:
user SPIKETJ, code 01234, code_id 85412974 and l_code_user SPIKETJ
declare
l_code_name table_2.cod_name%type;
l_code table_2.cod_emp%type;
l_code_id table_0.cod_id%type;
l_code_user table_03.cod_user%type;
begin
l_code_name := 'SPIKETJ';
l_code := '01234';
l_code_id := '85412974';
l_code_user := 'SPIKETJ';
DELETE table_2 WHERE cod_emp IN (l_code);
commit;
DELETE table_65 WHERE cod_emp IN (l_code);
commit;
DELETE table_41 WHERE cod_name IN (l_code_name);
commit;
DELETE table_18 WHERE cod_name IN (l_code_name);
commit;
DELETE table08 WHERE cod_user IN (l_code_name);
commit;
DELETE table_0 WHERE cod_docum IN (l_code_id);
commit;
DELETE table_17 WHERE cod_id IN (l_code_id);
commit;
DELETE table_03 WHERE cod_user IN (l_code_user);
commit;
END;
When I have to delete one user I only change/assign values for:
l_code_name, l_code, l_code_id, l_code_user.
But now, I have to delete almost 20 users!
So I wanna know if I have to run this query 20 times changing the variable values each time ?
OR
Can I write a query/block where running one time deletes the 20 users I wish to?
You can Create procedure as suggested above by Tony Andrews.
Procedure
Create or replace Procedure Delete_user
(l_code_name IN your_users.cod_name%type, -- Declare your IN parameters here
l_code IN your_users.cod_emp%type,
l_code_id IN your_users.cod_id%type,
l_code_user IN your_users.cod_user%type
)
AS
-- Declare your local variables
v_code_name your_users.cod_name%type := l_code_name;
v_code your_users.cod_emp%type := l_code;
v_code_id your_users.cod_id%type := l_code_id ;
v_code_user your_users.cod_user%type := l_code_user;
BEGIN
--- write your code(delete statements)
DELETE from your_users
WHERE cod_emp IN (v_code);
commit;
dbms_output.put_line( 'USER : ' || ' ' || v_code_user || ' is deleted.' );
---
--- similarly other delete statements
END DELETE_USER;
Output:
Procedure created.
check for errors using below command :
Show errors;
no errors
Call your Procedure for deleting users :
BEGIN
DELETE_USER('SPIKETJ',01234,85412974,'SPIKETJ');
DELETE_USER('JACKET',99999,111111,'JACKET');
--similary add other user details in order of the parameters declared in proc
END;
OUTPUT :
USER : SPIKETJ is deleted.
USER : JACKET is deleted.
Statement processed.
Read more here Procedures
For deleting users instead of calling procedure N (20) times.
Create or replace Procedure Delete_user
AS
v_code_id your_users.cod_id%type;
v_code_user your_users.cod_user%type ;
cursor C_users is select cod_id,cod_user from your_users
where 1=1; -- add condition to select users you wish to delete
BEGIN
OPEN C_users;
loop
Fetch C_users into v_code_id,v_code_user;
exit when C_users%NOTFOUND;
DELETE from your_users WHERE cod_emp IN (v_code_id); -- use your primary key
--- write delete statements for other tables
dbms_output.put_line( 'USER : ' || ' ' || v_code_user || ' is deleted.' );
End Loop;
commit;
Close C_users ;
END DELETE_USER;
-- Procedure created.
Output:
USER : mahi is deleted.
USER : xyz is deleted.
Statement processed.

Yet another bad bind variable in triggers

getting a bad bind variable "old.seatsremain" and "new.seatsremain". Trying to make this trigger automatically decrease the number of seats for the offering if the seats are available for the particular offering. Do I have to declare all the variables with the : in front of it???? my two tables are:
enrollments
{sid number,
offerno number;)
and
offering
{offerno number,
courseno varchar2(10),
instructor varchar2(10),
seatsremain number;}
UPDATED CODE TESTING: OK so I incorporated the coding Fumble gave me and it cleared the errors for ones that originally popped up however I tested out this new code and I still have some remaining error because of the EXCEPTION clause which I have no idea why because the syntax I double checked should be right. any ideas??
create or replace trigger enroll_bef_ins_row
before insert on enrollments
for each row
declare
originstruct offering.instructor%type;
origcourseno offering.courseno%type;
original offering.seatsremain%type;
seatsremain_already_full exception;
begin
Select seatsremain, instructor, courseno into original, originstruct, origcourseno from offering where offerno= :new.offerno;
if original > 0 then
update offering set seatsremain= seatsremain - 1;
dbms_output.put_line ('Seats available in offering'||offerno||'have decreased from'||original|| 'to' ||(seatsremain));
else if original = 0 then
raise seatsremain_already_full;
dbms_output.put_line ('Offering'||offerno||'is already full!');
else
update offering set offerno = :old.offerno;
update offering set courseno = origcourseno;
update offering set instructor = originstruct;
update offering set seatsremain = original;
end if;
exception
when seatsremain_already_full then
raise_application_error (-20001, 'Cannot allow insertion');
commit;
end;
/
THIS ERROR SHOWS UP NOW:
PLS-00103: Encountered the symbol "EXCEPTION" when expecting one
of the following:
begin case declare end exit for goto if loop mod null pragma
raise return select update while with
<<
close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe
PLS-00103: Encountered the symbol "end-of-file" when expecting
one of the following:
end not pragma final instantiable order overriding static
You are using correlation names from a table other than the one your trigger is created for. Try declaring oldSeatsRemain and NewSeatsRemain as variables within your trigger.
Try this (note: this sample has not been executed). It includes the edits I described in my comments.
create or replace trigger enroll_bef_ins_row
before insert on enrollments
for each row
declare
offerrow offering%rowtype;
seatsremain_already_full exception;
pragma autonomus_transaction;
begin
Select seatsremain into offerrow from offering where offerno= :new.offerno;
if offerrow.seatsremain > 0 then
update offering set seatsremain= offerrow.seatsremain - 1;
dmbs_output.put_line ('Seats available in offering ' |offerno| ' have decreased from ' |offerrow.seatsremain| ' to ' |offerrow.seatsremain-1|);
else if original = 0 then
dbms_output.put_line ('Offering ' |offerno| ' is already full!');
raise seatsremain_already_full;
else
insert into offering
values(offering.offerno,offering.courseno,offering.instructor,offering.seatsremain);
end if;
commit;
exception
when seatsremain_already_full
raise_application_error (-20001, 'Cannot allow insertion');
end
/

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;