Package and Procedure Compilation - sql

Struggling for over an hour now... Why won't this compile?
The Body compiles fine:
create or replace package body "PKG_CUSTOMER" is
PROCEDURE Create_Customer
( pr_customer_id customer.Customer_id%type,
pr_country customer.country%type,
pr_first_name customer.first_name%type,
pr_last_name customer.last_name%type,
pr_birth_date customer.birth_date%type,
pr_customer_type customer.customer_type%type,
pr_address customer.address%type)
IS
BEGIN
INSERT INTO customer (Customer_ID,Country,First_Name,Last_Name,Birth_Date,Customer_Type,Address)
VALUES(pr_customer_id, pr_country, pr_first_name, pr_last_name, pr_birth_date, pr_customer_type, pr_address);
END Create_Customer;
PROCEDURE Delete_Customer(pr_customer_id customer.customer_id%type) IS
BEGIN
DELETE FROM order_line WHERE fk1_order_id IN (SELECT order_id FROM placed_order WHERE fk1_customer_id = pr_customer_id);
DELETE FROM placed_order WHERE fk1_customer_id = pr_customer_id;
DELETE FROM customer WHERE customer_id = pr_customer_id;
END Delete_Customer;
end "PKG_CUSTOMER";​
But the specification won't compile:
create or replace package PKG_CUSTOMER as
Procedure CREATE_CUSTOMER;
Procedure DELETE_CUSTOMER;
end;​
I am getting this error:
Compilation failed,line 3 (21:20:46)
PLS-00323: subprogram or cursor 'CREATE_CUSTOMER' is declared in a package specification and must be defined in the package bodyCompilation failed,line 4 (21:20:46)
PLS-00323: subprogram or cursor 'DELETE_CUSTOMER' is declared in a package specification and must be defined in the package body
I'm using Oracle APEX.

The package specification has to provide the complete specification for the procedures that you want to expose. That includes the parameters. Assuming that you want to make both of the procedures that you declared in the package available to callers outside of the package
create or replace package PKG_CUSTOMER
as
Procedure CREATE_CUSTOMER(
pr_customer_id customer.Customer_id%type,
pr_country customer.country%type,
pr_first_name customer.first_name%type,
pr_last_name customer.last_name%type,
pr_birth_date customer.birth_date%type,
pr_customer_type customer.customer_type%type,
pr_address customer.address%type);
Procedure DELETE_CUSTOMER(pr_customer_id customer.customer_id%type);
end;​
If your intention is to declare a CREATE_CUSTOMER and a DELETE_CUSTOMER procedure that each accept 0 arguments, you would need to implement those procedures in the package body as well.

Related

Component must be declared error (ORA-06550)

Im getting this error:
ORA-06550:line 1, column 25:PLS-00302: component PA_EXCEPTION_LIST_UPDATE must be declared: line1, column 7:PL/SQL: Statement ignored.
I can't figure out what i did wrong.
PROCEDURE Pa_exception_list_update (p_ceid collection_entities.ceid%TYPE,
p_idusr users.idusr%TYPE
)
IS
v_idusr users.idusr%TYPE;
v_ceid collection_entities.ceid%TYPE;
BEGIN
INSERT INTO pa_exception_list(pa_exception_list_id,
ceid,
creation_date,
created_by)
VALUES(pa_exception_list_seq.nextval, p_ceid, SYSDATE, p_idusr);
END Pa_exception_list_update;
It looks like you are calling the procedure before it has been declared.
Look at this example.
Procedure A calls procedure B. But B is unknown at that moment.
create or replace package test is
begin
end test;
create or replace package body test is
procedure a
is
begin
b;
end;
procedure b is
begin
-- do someting
end;
end test;
Solution. Change the order of the procedures within the package or place the procedure in the package specification.
create or replace package test is
begin
procedure b;
end test;
create or replace package body test is
procedure a
is
begin
b;
end;
procedure b is
begin
-- do someting
end;
end test;
According error message the error appears at line 1.
In case this is a stand-alone procedure you must write like create or replace procedure Pa_exception_list_update ...
In case this is part of a PL/SQL Package then you must write like
CREATE OR REPLACE PACKAGE BODY <package name> AS
procedure Pa_exception_list_update ...
I think you are missing something when you declare.
p_ceid IN collection_entities.ceid%TYPE,
p_idusr IN users.idusr%TYPE
I also faced the same problem.
After checking I found that, the procedure i was calling did not exist in the package!.
Later changed the procedure name and it worked.

RESTRICT_REFERENCES and triggers

I add a PRAGMA RESTRICT_REFERENCES to a procedure in a package (for example, RNPS). That procedure implementation inserts a row in a table.
That table has a before insert trigger. that trigger reads a variable from a package and puts it :new.my_column.
I can compile the package body without problems, even though it seems like it is actually reading values from a package variable.
When I execute the procedure, it actually works. But this is the development eviroment, where there are no multiple simultaneous connections usually. I'm afraid that this could fail in the production enviroment.
So, should I be worried, or will this actually work?
Example code:
CREATE TABLE MY_TABLE
(
ID VARCHAR2(20) NOT NULL
, USER_ID VARCHAR2(50)
, CONSTRAINT MY_TABLE_PK PRIMARY KEY
(
ID
)
ENABLE
);
CREATE OR REPLACE PACKAGE PUSER IS
PROCEDURE saveUser(
pUserId VARCHAR2
);
PRAGMA RESTRICT_REFERENCES (saveUser, WNDS, RNDS, RNPS);
FUNCTION getUser RETURN VARCHAR2;
PRAGMA RESTRICT_REFERENCES (getUser, WNDS, RNDS, WNPS);
END PUSER;
CREATE OR REPLACE PACKAGE BODY PUSER AS
userId VARCHAR2(50);
PROCEDURE saveUser(
pUserId VARCHAR2
) IS
BEGIN
userId := pUserId;
END saveUser;
FUNCTION getUser RETURN VARCHAR2 IS
BEGIN
RETURN userId;
END getUser;
END PUSER;
CREATE OR REPLACE PACKAGE MY_PACKAGE IS
PROCEDURE insertMyTable(
pId VARCHAR2
);
PRAGMA RESTRICT_REFERENCES (insertMyTable, RNPS);
END MY_PACKAGE;
CREATE OR REPLACE PACKAGE BODY MY_PACKAGE AS
PROCEDURE insertMyTable(
pId VARCHAR2
) IS
BEGIN
INSERT INTO MY_TABLE(id) VALUES(pId);
END insertMyTable;
END MY_PACKAGE;
CREATE OR REPLACE TRIGGER MY_TABLE_TRIGGER
BEFORE INSERT ON MY_TABLE FOR EACH ROW
DECLARE
BEGIN
:new.USER_ID := PUSER.getUser;
END MY_TABLE_TRIGGER;
Edit: I know that RESTRICT_REFERENCES is deprecated, but knowing this would still be useful for already existing code.
RNPS is useful only for functions as it tells the compiler that the return value of a function will not have changed between calls if in the meanwhile no package state (or database state for the other pragmas) has been changed. It allows for caching, but in a more implicit way than with RESULT_CACHE or DETERMINISTIC (which is now the preferred way to tell the optimizer what to expect).
Since you use it on a procedure, it does not really matter. You can safely proceed without changing package specifications. However, since you (or your successor) might ask yourselves the same question a year from now, you might as well change the packages right now while you're at it.

Returning a cursor from a procedure

I am trying to return a cursor from a procedure which will display the contents of a table invoice. The package and procedure was successfully created but when I run the line - Exec CursorPckg.CursorTest_Proc(); I get the following error component 'CURSORTEST_PROC' must be declared any ideas? I'm running SQL*PLUS
CREATE or REPLACE PACKAGE CursorPckg
IS
TYPE salary_type IS REF CURSOR RETURN Invoice%ROWTYPE;
END CursorPckg;
CREATE OR REPLACE PACKAGE BODY CursorPckg AS
PROCEDURE CursorTest_Proc (c1 OUT CursorPckg.salary_type)
IS
BEGIN
OPEN c1 FOR
SELECT * FROM Invoice;
END CursorTest_Proc;
END CursorPckg;
You need to add procedure declaration in package declaration for it to be accessible from outside your package.
Like this:
CREATE or REPLACE PACKAGE CursorPckg
IS
TYPE salary_type IS REF CURSOR RETURN Invoice%ROWTYPE;
PROCEDURE CursorTest_Proc (c1 OUT CursorPckg.salary_type);
END CursorPckg;
Edit:
To answer Your comment. You cannot execute this procedure without providing parameter of type CursorPckg.salary_type.

How can I declare and initialize a package variable by SELECT

Can someone help me understand why I can declare a hardcoded date value in a package but not assign the value from a query? I've seen a number of examples (including the reference book) that show declarations of hardcoded values but I'm unable to locate examples of assigning values to variable through queries.
This is allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := '2012-05-30';
-- ...procedures follow
This is allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := sysdate;
This is not allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := select MAX(date_) from Table_Name;
I've tried a number of ways and I'm ok with it not working - I can use it as needed in the procedures themselves. But I'd like to know why I can't assign a value to tDate this way? The specific error is:
Encountered the symbol 'SELECT' when expecting ....
Adding that I can get variable values assigned through queries in a stored procedure but the same process does not seem to work for package body.
PROCEDURE Proc_Name IS
tDate Date;
BEGIN
SELECT MAX(date_) into tDate from Table_Name;
You need to create a package initialization block in your package body. This is a relatively arcane bit of PL/SQL package lore, but it's there and can be used. Here's an example:
CREATE OR REPLACE PACKAGE TEST_PKG IS
tDate DATE;
END TEST_PKG;
Here we've created a package spec which contains only a single DATE variable. Now we'll create a simple body which initializes that variable using a SELECT statement:
CREATE OR REPLACE PACKAGE BODY TEST_PKG IS
-- package-private variables go first (if any)
(...)
-- then the public functions/procedures
(...)
-- then a final BEGIN-END block which is the package initialization block
BEGIN
SELECT SYSDATE
INTO tDATE
FROM DUAL;
EXCEPTION -- you can have exception handlers in your initialization block
WHEN OTHERS THEN
NULL; -- pointless here, but this is just an example
RAISE;
END TEST_PKG;
Now if you execute the following:
begin
-- Test statements here
DBMS_OUTPUT.PUT_LINE('TEST_PKG.tDate = ' || TEST_PKG.tDate);
end;
it should print the current date.
Share and enjoy.
You need to use INTO when selecting directly into a variable.
select MAX(date_)
INTO tDate
from Table_Name;

Dynamic SQL not working as expected

create or replace procedure createtables
Authid current_user as
begin
execute immediate 'create table newcustomer as select * from customer';
end;
create or replace procedure e
is
begin
createtables;
select * from newcustomer;
end;
I got two procedures above. first one will create a new tables called newcustomer, second procedure will call the first procedure and query to the newcustomer table. when I try to compile this code, it says the table is not yet created, I don't really get it as I have called createtables procedure so I assume I have created the table.
Any help will be appreciated. Thanks
Compiling the second procedure without executing the first procedure first will fail, since the table has not been created.
You cannot compile a procedure that relies on objects that do not exist.
Use EXEC createtables before creating procedure e, and do not call createtables in there.
Procedure e will also not compile because you are not using the results of select * from newcustomer as cursor or store the results into variables.
EDIT:
Instead of procedures, you could use an anonymous block. Put the following into a file and execute it (via SQL*Plus for example):
Create Table newcustomer As Select * From customer;
Begin
Null; --# Do something with your new table in here.
End;
/