Packaged Procedure calling private function - unusable in SQL - oracle

I have a package with two private functions, and one procedure which calls them. The two functions return due dates (first, and last) of pledged payments to the procedure. The procedure returns those dates, along with the name, and ID of the donor.
CREATE OR REPLACE PACKAGE PLEDGE_PKG IS
PROCEDURE DD_PLIST_PP
(p_id IN DD_PLEDGE.IDPLEDGE%TYPE,
p_first DD_DONOR.FIRSTNAME%TYPE,
p_last DD_DONOR.LASTNAME%TYPE,
p_payfirst OUT DATE,
p_paylast OUT DATE);
END;
CREATE OR REPLACE PACKAGE BODY PLEDGE_PKG IS
-- Determines 1st Payment Due Date based on ID.
FUNCTION dd_paydate1_pf
(p_id IN dd_pledge.idpledge%TYPE)
RETURN DATE
IS
lv_pl_dat DATE;
lv_mth_txt VARCHAR2(2);
lv_yr_txt VARCHAR2(4);
BEGIN
SELECT ADD_MONTHS(pledgedate,1)
INTO lv_pl_dat
FROM dd_pledge
WHERE idpledge = p_id;
lv_mth_txt := TO_CHAR(lv_pl_dat,'mm');
lv_yr_txt := TO_CHAR(lv_pl_dat,'yyyy');
RETURN TO_DATE((lv_mth_txt || '-01-' || lv_yr_txt),'mm-dd-yyyy');
END dd_paydate1_pf;
-- Determines LAST Payment Due Date based on ID.
FUNCTION dd_payend_pf
(p_id IN dd_pledge.idpledge%TYPE)
RETURN DATE
IS
lv_pay1_dat DATE;
lv_mths_num dd_pledge.paymonths%TYPE;
BEGIN
SELECT dd_paydate1_pf(idpledge), paymonths - 1 -- LINE 28
INTO lv_pay1_dat, lv_mths_num
FROM dd_pledge
WHERE idpledge = p_id;
IF lv_mths_num = 0 THEN
RETURN lv_pay1_dat;
ELSE
RETURN ADD_MONTHS(lv_pay1_dat, lv_mths_num);
END IF;
END dd_payend_pf;
-- Displays Donor Name, ID, First, Last payment using Donor ID
PROCEDURE DD_PLIST_PP
(p_id IN DD_PLEDGE.IDPLEDGE%TYPE,
p_first DD_DONOR.FIRSTNAME%TYPE,
p_last DD_DONOR.LASTNAME%TYPE,
p_payfirst OUT DATE,
p_paylast OUT DATE)
AS
lv_first DD_DONOR.FIRSTNAME%TYPE;
lv_last DD_DONOR.LASTNAME%TYPE;
BEGIN
SELECT FIRSTNAME, LASTNAME
INTO lv_first, lv_last
FROM DD_DONOR
WHERE p_id = IDDONOR;
p_payfirst := PLEDGE_PKG.DD_PAYDATE1_PF(p_id);
p_paylast := PLEDGE_PKG.DD_PAYEND_PF(p_id);
END DD_PLIST_PP;
END;
The error I am getting is from the second block.
LINE/COL ERROR
-------- -----------------------------------------------------------------
28/3 PL/SQL: SQL Statement ignored
28/10 PL/SQL: ORA-00904: : invalid identifier
28/10 PLS-00231: function 'DD_PAYDATE1_PF' may not be used in SQL
SO requests more text and less code. :)
My issue is I want to call the PLEDGE_PKG.DD_PLIST_PP and get back the name, and the two dates back.
Hopefully this explanation helps, and thank you for the help.

As you marked it, line #28 is
SELECT dd_paydate1_pf(idpledge), paymonths - 1
This statement is SQL, and you use it within the package (which is PL/SQL). SQL engine can't access function which is private to that package, unless you make it public. Therefore, what would work is
what you used in line #55:
p_payfirst := PLEDGE_PKG.DD_PAYDATE1_PF(p_id);
as here everything is in PL/SQL (though, that's questionable as you're passing P_ID parameter's value from another table in line #28), or
make the function public by declaring it in package specification
[EDIT]
Based on your comment (too much text for replying through another comment):
It is about context switching. select dd_paydate1_pf ... is SQL called from PL/SQL. SQL tries to select function's result and can't find that function at the SQL layer. If you preceded it with package name, i.e. select pledge_pkg.dd_paydate1_pf..., it still wouldn't work as that function isn't declared in package specification but is private to package body.
What you could do is to rewrite function as:
FUNCTION dd_payend_pf
(p_id IN dd_pledge.idpledge%TYPE)
RETURN DATE
IS
lv_pay1_dat DATE;
lv_mths_num dd_pledge.paymonths%TYPE;
BEGIN
lv_pay1_dat := dd_paydate1_pf(p_id); --> move it out of SELECT
SELECT paymonths - 1
INTO lv_mths_num
FROM dd_pledge
WHERE idpledge = p_id;
IF lv_mths_num = 0 THEN
RETURN lv_pay1_dat;
ELSE
RETURN ADD_MONTHS(lv_pay1_dat, lv_mths_num);
END IF;
END dd_payend_pf;
See if it helps.

You cannot use the private function as part of a sql query. I've added this answer late as my first answer did not address the issue, and I can see that there is a good answer here now. But just for info and I hope it helps in regard to the DATE usage, here a fun package using a private function (that simply returns the day of the month that you are paid) and a public function that returns your next pay day:
create or replace package date_test is
-- public:
function pay_day return date;
end;
/
create or replace package body date_test is
-- private function:
function paid_on return number is
begin
return 24;
end;
-- public:
function pay_day return date is
-- use private function:
v_paydate_DD number := paid_on;
v_next_payday date;
begin
v_next_payday := trunc(sysdate,'MONTH') + v_paydate_DD - 1;
if trunc(sysdate) >= trunc(v_next_payday) then
select add_months(v_next_payday,1) into v_next_payday from dual;
end if;
return v_next_payday;
end;
end;
/
select date_test.pay_day from dual;

Related

Oracle Where with variable

I have problem with my plsql code. It's just part of my whole job.
declare
id number(2):=1; (here is function which returns any value)
check VARCHAR2(100);
begin
select COUNT(*) into check from T_SDSN_LOG Where ANY_ID=id AND CHECK LIKE
'NAME';
dbms_output.put_line(check);
end;
In this case, my select returns 0 althought it should be 2.
If I change the part
Where ANY_ID=id to
Where ANY_ID=2 it works perfectly. Any advices? I need id to be variable as a return value from function.
This uses a locally defined function so it isn't available in the SQL but can be referenced in the PL/SQL.
DECLARE
lnum_id NUMBER := return_id;
lnum_check VARCHAR2(100);
FUNCTION return_id
RETURN NUMBER
IS
BEGIN
RETURN 123456;
END;
BEGIN
lnum_id := return_id;
SELECT COUNT(*)
INTO lnum_check
FROM my_table
WHERE table_id = lnum_id;
DBMS_OUTPUT.put_line(lnum_check);
END;
You will presumably want this functionality in a package in which case you can declare the function in the package header, write the code for the function in the body and then reference it in the SQL itself. So if I declare a function (FNC_RETURN_ID) in a package called PKG_DATA_CHECKS that returns a NUMBER I can do the following;
DECLARE
lnum_id NUMBER;
lnum_check VARCHAR2(100);
BEGIN
SELECT COUNT(*)
INTO lnum_check
FROM my_table
WHERE table_id = (SELECT pkg_data_checks.fnc_return_id FROM dual);
DBMS_OUTPUT.put_line(lnum_check);
END;

ORA-24344: success with compilation error in Oracle Apex when compiling a package

I'm working on my university database project in Oracle Apex and I'm getting the ORA-24344: success with compilation error when trying to compile the body package with the following code:
CREATE OR REPLACE PACKAGE BODY band_price_package AS
-- Function that checks if a band has a manager
FUNCTION agent_present(band_id BAND.Band_id%TYPE)
RETURN BOOLEAN
IS
BEGIN
IF BAND.Agent_firstname IS NULL
AND BAND.Agent_lastname IS NULL
AND BAND.Agent_phone IS NULL
AND BAND.Agent_email IS NULL
THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END agent_present;
-- Procedure that gets the band hire price including agent fee
PROCEDURE get_band_cost(band_id IN BAND.Band_id%TYPE,
band_cost OUT BOOKING.Agreed_band_price%TYPE)
IS
BEGIN
IF agent_present(band_id)
THEN
band_cost := BOOKING.Agreed_band_price * 1.25;
ELSE
band_cost := BOOKING.Agreed_band_price;
END IF;
END get_band_cost;
END band_price_package;
/
The following specification has compiled without any errors:
CREATE OR REPLACE PACKAGE band_price_package AS
-- Function that checks if a band has a manager
FUNCTION agent_present(band_id BAND.Band_id%TYPE)
RETURN BOOLEAN;
-- Procedure that gets the band hire price including agent fee
PROCEDURE get_band_cost(band_id IN BAND.Band_id%TYPE,
band_cost OUT BOOKING.Agreed_band_price%TYPE);
END band_price_package;
/
You cannot access tables directly in PL/SQL; so
IF BAND.Agent_firstname IS NULL
AND BAND.Agent_lastname IS NULL
AND BAND.Agent_phone IS NULL
AND BAND.Agent_email IS NULL
is invalid syntax. You need to use an SQL statement inside the PL/SQL block like this:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE BAND(
band_id INTEGER PRIMARY KEY,
Agent_firstname VARCHAR2(200),
Agent_lastname VARCHAR2(200),
Agent_phone VARCHAR2(20),
Agent_email VARCHAR2(100)
)
/
CREATE TABLE BOOKING(
Agreed_band_price NUMBER(10,2)
)
/
CREATE OR REPLACE PACKAGE band_price_package AS
-- Function that checks if a band has a manager
FUNCTION agent_present(
i_band_id BAND.Band_id%TYPE
)
RETURN BOOLEAN;
-- Procedure that gets the band hire price including agent fee
PROCEDURE get_band_cost(
i_band_id IN BAND.Band_id%TYPE,
o_band_cost OUT BOOKING.Agreed_band_price%TYPE
);
END band_price_package;
/
Then you can do:
CREATE OR REPLACE PACKAGE BODY band_price_package AS
-- Function that checks if a band has a manager
FUNCTION agent_present(
i_band_id BAND.Band_id%TYPE
)
RETURN BOOLEAN
IS
agent_exists NUMBER(1,0);
BEGIN
-- Check if an agent exists in the table.
SELECT 1
INTO agent_exists
FROM band
WHERE band_id = i_band_id
AND ( Agent_firstname IS NOT NULL
OR Agent_lastname IS NOT NULL
OR Agent_phone IS NOT NULL
OR Agent_email IS NOT NULL );
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN FALSE;
END agent_present;
-- Procedure that gets the band hire price including agent fee
PROCEDURE get_band_cost(
i_band_id IN BAND.Band_id%TYPE,
o_band_cost OUT BOOKING.Agreed_band_price%TYPE
)
IS
BEGIN
-- Not completed; since it is your homework.
NULL;
END get_band_cost;
END band_price_package;
/
Which tool do you use to access database? In SQLPlus, you should run SHOW ERR which would tell you what's wrong with your code. For example:
SQL> create or replace function f_test return boolean is
2 begin
3 return 1 = 2 --> missing semi-colon
4 end;
5 /
Warning: Function created with compilation errors.
SQL> show err
Errors for FUNCTION F_TEST:
LINE/COL ERROR
-------- -----------------------------------------------------------------
4/1 PLS-00103: Encountered the symbol "END" when expecting one of the
following:
* & - + ; / at mod remainder rem <an exponent (**)> and or ||
multiset
The symbol ";" was substituted for "END" to continue.
SQL>
GUI tools usually have the "Errors" tab in Object Navigator which display the same.
As of your code, a few blind guesses (as we don't have your tables and can't test it ourselves):
function AGENT_PRESENT accepts BAND_ID as a parameter, which is declared as a BAND.BAND_ID column datatype. You can't reference other BAND table columns the way you do - you should SELECT those columns into variables (or, if you want, declare a rowtype variable and work with it) and then detect whether they are present or not.
procedure GET_BAND_COST calls BOOKING.AGREED_BAND_PRICE which is unknown (at least, it is not declared in that package). So, what is it? Is it a table column? If so, you can't modify it that way - use UPDATE instead.

Oracle extract method returns same output for different rows [duplicate]

I need to extract and display all the years for all the records in db using member function in oracle 11g.
CREATE or replace TYPE BODY student_t AS
MEMBER FUNCTION getYear RETURN SYS_REFCURSOR IS
yearDOB SYS_REFCURSOR;
BEGIN
for c in (SELECT EXTRACT(YEAR FROM s.dob) c_year from student s)
loop
yearDOB := c.c_year;
end loop;
return yearDOB;
END;END;/
Since I need to return multiple values for the extract function I have declared a SYS_REFCURSOR type variable to return. But it will generate following errors.
7/1 PL/SQL: Statement ignored,
7/14 PLS-00382: expression is of wrong type
output after changed as following answer.
S.GETYEAR()
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
C_YEAR
1993
1995
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
C_YEAR
1993
1995
student type as follows.
create type student_t as object(
stno char(4),
dob date)/
alter type student_t add member function getYear RETURN SYS_REFCURSOR cascade;/
I'm not sure what you are trying to achieve but I think you misunderstood concept of object in oracle.
In my example i'm assuming
1) Table studens is create with this script create table student of student_t;
drop table student;
Create type spec and body.
create or replace type student_t as object(
stno char(4),
dob date,
member function getYear return number
)
;
create or replace type body student_t as
member function getYear return number is
begin
return EXTRACT(YEAR FROM self.dob);
end;
end;
Create table of studnets
create table student of student_t;
Populate tabel
declare
v_student student_t;
begin
for i in 0 .. 10 loop
insert into student values(student_t('ST'||+mod(i,3),to_date('01-01-'||to_char(2000+i),'dd-mm-yyyy')));
end loop;
commit;
end;
And the query.
select s.*,s.getYear() from student s;
It is much simpler:
yearDOB SYS_REFCURSOR;
BEGIN
OPEN yearDOB for
SELECT EXTRACT(YEAR FROM s.dob) c_year
from student s;
return yearDOB;
END;

How to use session-global variables of type collection in oracle

I have a package which declares a collection of type table of some database table's %rowtype. It also declares a function to populate the package-level variable with some data. I can now print the data with dbms_output, seems fine.
But when I use the package-level variable in some sql I get the following error:
ORA-21700: object does not exist or is marked for delete
ORA-06512: at "TESTDB.SESSIONGLOBALS", line 17
ORA-06512: at line 5
Here is my code:
create some dummy data:
drop table "TESTDATA";
/
CREATE TABLE "TESTDATA"
( "ID" NUMBER NOT NULL ENABLE,
"NAME" VARCHAR2(20 BYTE),
"STATUS" VARCHAR2(20 BYTE)
);
/
insert into "TESTDATA" (id, name, status) values (1, 'Hans Wurst', 'J');
insert into "TESTDATA" (id, name, status) values (2, 'Hans-Werner', 'N');
insert into "TESTDATA" (id, name, status) values (3, 'Hildegard v. Bingen', 'J');
/
now create the package:
CREATE OR REPLACE
PACKAGE SESSIONGLOBALS AS
type t_testdata is table of testdata%rowtype;
v_data t_testdata := t_testdata();
function load_testdata return t_testdata;
END SESSIONGLOBALS;
and the package body:
CREATE OR REPLACE
PACKAGE BODY SESSIONGLOBALS AS
function load_testdata return t_testdata AS
v_sql varchar2(500);
BEGIN
if SESSIONGLOBALS.v_data.count = 0
then
v_sql := 'select * from testdata';
execute immediate v_sql
bulk collect into SESSIONGLOBALS.v_data;
dbms_output.put_line('data count:');
dbms_output.put_line(SESSIONGLOBALS.v_data.count);
end if; -- SESSIONGLOBALS.v_data.count = 0
-- ******************************
-- this line throws the error
insert into testdata select * from table(SESSIONGLOBALS.v_data);
-- ******************************
return SESSIONGLOBALS.v_data;
END load_testdata;
END SESSIONGLOBALS;
execute the sample:
DECLARE
v_Return SESSIONGLOBALS.T_TESTDATA;
BEGIN
v_Return := SESSIONGLOBALS.LOAD_TESTDATA();
dbms_output.put_line('data count (direct access):');
dbms_output.put_line(SESSIONGLOBALS.v_data.count);
dbms_output.put_line('data count (return value of function):');
dbms_output.put_line(v_Return.count);
END;
If the line marked above is commented out i get the expected result.
So can anyone tell me why the exception stated above occurs?
BTW: it is absolutely nessecary for me to execute the statement which populates the collection with data as dynamic sql because the tablename is not known at compiletime. (v_sql := 'select * from testdata';)
the solution is to use pipelined functions in the package
see: http://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dcitblfns.htm#CHDJEGHC ( => section Pipelining Between PL/SQL Table Functions does the trick).
my package looks like this now (please take the table script from my question):
create or replace
PACKAGE SESSIONGLOBALS AS
v_force_refresh boolean;
function set_force_refresh return boolean;
type t_testdata is table of testdata%rowtype;
v_data t_testdata;
function load_testdata return t_testdata;
function get_testdata return t_testdata pipelined;
END SESSIONGLOBALS;
/
create or replace
PACKAGE BODY SESSIONGLOBALS AS
function set_force_refresh return boolean as
begin
SESSIONGLOBALS.v_force_refresh := true;
return true;
end set_force_refresh;
function load_testdata return t_testdata AS
v_sql varchar2(500);
v_i number(10);
BEGIN
if SESSIONGLOBALS.v_data is null then
SESSIONGLOBALS.v_data := SESSIONGLOBALS.t_testdata();
end if;
if SESSIONGLOBALS.v_force_refresh = true then
SESSIONGLOBALS.v_data.delete;
end if;
if SESSIONGLOBALS.v_data.count = 0
then
v_sql := 'select * from testdata';
execute immediate v_sql
bulk collect into SESSIONGLOBALS.v_data;
end if; -- SESSIONGLOBALS.v_data.count = 0
return SESSIONGLOBALS.v_data;
END load_testdata;
function get_testdata return t_testdata pipelined AS
v_local_data SESSIONGLOBALS.t_testdata := SESSIONGLOBALS.load_testdata();
begin
if v_local_data.count > 0 then
for i in v_local_data.first .. v_local_data.last
loop
pipe row(v_local_data(i));
end loop;
end if;
end get_testdata;
END SESSIONGLOBALS;
/
now i can do a select in sql like this:
select * from table(SESSIONGLOBALS.get_testdata());
and my data collection is only populated once.
nevertheless it is quite not comparable with a simple
select * from testdata;
from a performace point of view but i'll try out this concept for some more complicated use cases. the goal is to avoid doing some really huge select statements involving lots of tables distributed among several schemas (english plural for schema...?).
The syntax you use does not work:
insert into testdata select * from table(SESSIONGLOBALS.v_data); -- does not work
You have to use something like that:
forall i in 1..v_data.count
INSERT INTO testdata VALUES (SESSIONGLOBALS.v_data(i).id,
SESSIONGLOBALS.v_data(i).name,
SESSIONGLOBALS.v_data(i).status);
(which actually duplicates the rows in the table)
Package-level types cannot be used in SQL. Even if your SQL is called from within a package, it still can't see that package's types.
I'm not sure how you got that error message, when I compiled the package I got this error, which gives a good hint at the problem:
PLS-00642: local collection types not allowed in SQL statements
To fix this problem, create a type and a nested table of that type:
create or replace type t_testdata_rec is object
(
"ID" NUMBER,
"NAME" VARCHAR2(20 BYTE),
"STATUS" VARCHAR2(20 BYTE)
);
create or replace type t_testdata as table of t_testdata_rec;
/
The dynamic SQL to populate the package variable gets more complicated:
execute immediate
'select cast(collect(t_testdata_rec(id, name, status)) as t_testdata)
from testdata ' into SESSIONGLOBALS.v_data;
But now the insert will work as-is.

Creating a package to keep track of tapes used

Thought I had followed creation pattern, but the body will not compile. What I am trying to accomplish is to develop a package to run a procedrure periodically to determine at what time and date more than 15 are in use.. Oracle 11g.
The only other data that needs to go into the table beingg inserted into the sysdate.
CREATE OR REPLACE
PACKAGE TAPES_USED AS
function TAPESCOUNT(count number) return number;
procedure INSERT_TAPES_COUNT(sysdate date, count NUMBER);
END TAPES_USED;
/
-----------------------------------------
CREATE OR REPLACE
PACKAGE body TAPES_USED AS
function TAPESCOUNT(count number) return number as count number;
begin
select count(*)
into
count
from DEV.TAPES_IN USE where count(*) > 15;
procedure INSERT_TAPES_COUNT(sysdate date, count NUMBER)as
begin
INSERT INTO DEV.TAPES_USED VALUES
(sysdate, count);
end INSERT_TAPES_COUNT;
END TAPES_USED;
/
Any help or suggestion anyone can offer will be appreciated.
CREATE OR REPLACE
PACKAGE BODY tapes_used AS
FUNCTION tapescount(in_ct NUMBER) RETURN NUMBER IS
ct NUMBER;
BEGIN
SELECT COUNT(*)
INTO ct
FROM dev.tapes_in_use;
IF ct > in_ct THEN
RETURN ct;
ELSE
RETURN NULL;
END IF;
END tapescount;
PROCEDURE insert_tapes_count(sysdt date, ct NUMBER) IS
BEGIN
INSERT INTO dev.tapes_used VALUES (sysdt, ct);
END insert_tapes_count;
END tapes_used;
/
You should refrain from using reserved words such as COUNT and SYSDATE for variable names (I don't know but that could be some of your compilation issues), so I've renamed them. Also, you forgot to END your function. I think you were missing an underscore in your table name in the FROM clause of the SELECT in your function, and you didn't have a RETURN statement in your function, which you must have.
Generally speaking, a function should accept one or more input parameters and return a single value. You're not making use of the input parameter in your function. I've implemented a suggested parameter.
As Egor notes, this isn't a realistic function, and I'm not certain about your intent here. What is the function supposed to do?
Maybe you want your function to return the Date/Time your count was exceeded? You could also combine everything into a single procedure:
PROCEDURE ck_tape_ct(min_tape_ct NUMBER) IS
ct NUMBER;
BEGIN
SELECT COUNT(*)
INTO ct
FROM dev.tapes_in_use;
IF ct > min_tape_ct THEN
INSERT INTO dev.tapes_used VALUES(SYSDATE, ct);
END IF;
END;

Resources