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;
Related
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;
I'm newbie in stored procedures and I create a stored procedure, but when I run it by user input, I get an error; but when get value to variable daynumber, it is working.
Suggetions from SQL Developer are:
*Cause: This typically happens if there is infinite recursion in the PL/SQLfunction that is being executed.
*Action: User should alter the recursion condition in order to prevent infinite recursion.
How can I solve it?
create or replace procedure P_SiteNumber_Range_D(Sitenum NUMBER) is
daynumber number;
begin
p_sitenumber_range_d(Sitenum => daynumber);
-- daynumber := 2;
for l in (select PROVINCE from v_sitenumber_D_province_range)
loop
update PM4h_db.IND_D_3102
set IND_D_3102_029 =
(select countsite from some table where l1.province=province );
end loop;
end P_SiteNumber_Range_D;
Run procedure as :
DECLARE
SITENUM NUMBER;
BEGIN
SITENUM := 3;
P_SITENUMBER_RANGE_D(
SITENUM => SITENUM
);
END;
This procedure doesn't make much sense (at least, to me).
you are passing sitenum and never do anything with it; should it be used in where clause in cursor for loop and/or update statement?
this is a procedure, and then - in line #4 of your original code - you are calling itself (which then calls itself which calls itself etc., until Oracle stops it and returns an error)
the most obvious "solution" is to remove that statement:
p_sitenumber_range_d(Sitenum => daynumber);
but that probably won't be all, because of my first objection
Furthermore, maybe you don't need the loop at all, as the whole code can be rewritten as
create or replace procedure p_sitenumber_range_d (par_sitenum in number)
is
begin
update pm4h_db.ind_d_3102 set
ind_d_3102_029 = (select countsite
from some_table
where province = (select province
from v_sitenumber_d_province_range
where sitenum = par_sitenum
)
);
end;
It might, or might not work - there's a possibility of TOO_MANY_ROWS if select returns more than a single value. I don't know, as I don't have your tables, so - that might need to be fixed.
If you insist on the loop, then consider such a code:
create or replace procedure p_sitenumber_range_d (par_sitenum in number)
is
begin
for cur_r in (select province
from v_sitenumber_d_province_range
where sitenum = par_sitenum
)
loop
update pm4h_db.ind_d_3102 set
ind_d_3102_029 = (select countsite
from some_table
where province = cur_r.province
);
end loop;
end;
Are you aware that you've built in a recursion?
The first thing you do during the procedure is to call up the procedure itself!
Can you call a PL/SQL procedure from inside a function?
I haven't come across with the practical example.So if anyone has come across with this please share.
Yes. You can call any pl/sql program from inside any other pl/sql program. A function can call a function, a procedure can call a procedure which calls a function, a function can invoke a TYPE BODY...do an INSERT..which causes a TRIGGER to fire.
A simple (not really practical) example.
Using the HR schema where you have an EMPLOYEES table which includes columns EMPLOYEE_ID, FIRST_NAME, and LAST_NAME.
I have a function that takes in an INTEGER which we use to look up an EMPLOYEE record. We take their first and last names, and concat them together, and return the value back in UPPERCASE text.
Before we do that however, we call a procedure which does nothing but take a 5 second nap using the DBMS_LOCK package.
The code:
create or replace procedure do_nothing_comments (x in integer, y in integer)
is
begin
null;
-- yeah, this is a dumb demo
dbms_lock.sleep(5);
end;
/
create or replace FUNCTION upper_name (
x IN INTEGER
) RETURN VARCHAR2 IS
upper_first_and_last VARCHAR2 (256);
BEGIN
SELECT upper (first_name)
|| ' '
|| upper (last_name)
INTO upper_first_and_last
FROM employees
WHERE employee_id = x;
do_nothing_comments (1, 2); -- here we are calling the procedure
RETURN upper_first_and_last;
END;
/
Now let's invoke the function.
DECLARE
X NUMBER;
v_Return VARCHAR2(200);
BEGIN
X := 101;
v_Return := UPPER_NAME(
X => X
);
:v_Return := v_Return;
END;
/
I'm going to do this in SQL Developer using the Execute feature with the function open:
I get the answer back...it just takes 5 seconds longer than it really needed to.
Here you go:
create or replace function demo
return varchar2
as
begin
dbms_output.put_line('Hello');
return 1;
end demo;
I am working on an application and made the decision that all the queries would be procedures. I hope to have gains in performance and ease of maintenance by doing it this way. Our DBA's have also expressed interest in having it done this way.
I have an HR table where operations are performed on it each night, and any changes are recorded in a secondary table. We are not doing auditing, these change records are kept until the next run and show users the changes that have happened.
To keep my question shorter I have reduced the number of columns in HR.
The HR table ID, GROUP_NAME, and GROUP_LEVEL. The Drill table has ID and TYPEVALUE.
CREATE OR REPLACE PROCEDURE DOCSADM.DRILL_RECORD_POSITION (
RECORD_TYPE IN VARCHAR2,
OUT_ID OUT VARCHAR2,
OUT_GROUP_NAME OUT VARCHAR2,
OUT_GROUP_LEVEL OUT VARCHAR2
) AS
BEGIN
SELECT HR.ID, HR.GROUP_NAME, HR.GROUP_LEVEL
INTO OUT_ID, OUT_GROUP_NAME, OUT_GROUP_LEVEL
FROM HR_POSITION HR JOIN DRILL_POSITION DP ON (HR.ID = DP.ID) WHERE DP.TYPEVALUE = RECORD_TYPE;
END DRILL_RECORD_POSITION;
The procedure compiles without issue. Before doing all the work in the application to link to the procedure and extract the values which in this case will eventually be displayed in a view or webpage, I wanted to have a quick little script that would call the procedure and then display the results so I can verify in Oracle.
Loops
BEGIN
for t in (DRILL_RECORD_POSITION('D', V1,V5,V6))
loop
--dbms_output.put_line(t.V1 || t.V5 || t.V6);
dbms_output.put_line(t.OUT_ID);
end loop;
END;
/
CURSORS
DECLARE
V1 HR_POSITION.ID%TYPE;
V5 HR_POSITION.GROUP_NAME%TYPE;
V6 HR_POSITION.GROUP_LEVEL%TYPE;
CURSOR T_CUR IS DRILL_RECORD_POSITION('D', V1,V5,V6);
BEGIN
OPEN T_CUR;
DBMS_OUTPUT.PUTLINE('START');
LOOP
FETCH T_CUR INTO V1,V5,V6;
EXIT WHEN T_CUR%NOTFOUND;
DBMS_OUTPUT.PUTLINE(V1||V5||V6);
END LOOP;
CLOSE T_CUR;
END;
FOR LOOPS
DECLARE
V1 HR_POSITION.POSITION_ID%TYPE;
V5 HR_POSITION.GROUP_NAME%TYPE;
V6 HR_POSITION.GROUP_LEVEL%TYPE;
BEGIN
DBMS_OUTPUT.PUTLINE('START');
FOR INDEX IN (DRILL_RECORD_POSITION('D', V1,V5,V6))
LOOP
--DBMS_OUTPUT.PUTLINE(INDEX.ID);
DBMS_OUTPUT.PUTLINE(INDEX.V1||INDEX.V5||INDEX.V6);
END LOOP;
END;
Note: I edited the column names out and shorted some when transferring here so I might have made a few mistakes.
All the articles I have seen online show me how to display from within the original procedure or by using views, cursors, records. Unless I am wrong, Eclipse wont have any problems using the information in the current form which is why I am passing it that way. So I am not interested in changing the procedure and would like to work with it as is, since thats how the application will be doing it.
As this is the first of the stored procedures I am doing for the application, instead of using adhoc queries from the application, I dont have any existing examples to work from, which is why I believe the results will work fine, because it should be the same format the adhoc ones use.
Update:
In one of the comments, I was pointed to what should have been a solution. This was confirmed by another solution that was under it.
I keep getting the error
ORA-01422: exact fetch returns more than requested number of rows
So Im returning multiple rows, but that is my expectation and what is happening. I just cant seem to figure out how to display the results.
To test the procedure you showed, you would do something like:
declare
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
drill_record_position('D', l_id, l_group_name, l_group_level);
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end;
/
But that - or more specifically, your procedure - only works if there is exactly one row in the query's result set for the passed-in value type. It seems you're expecting multiple rows back (which would get too-many-rows), but there could also be non (which would get no-data-found).
So really it seems like your question should be about how to write your procedure so it works with one of the retrieval/test methods you tried.
If your procedure needs to return multiple rows then it can use a ref cursor, e.g.:
create or replace procedure drill_record_position (
p_record_type in varchar2,
p_ref_cursor out sys_refcursor
)
as
begin
open p_ref_cursor for
select hr.id, hr.group_name, hr.group_level
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type;
end drill_record_position;
/
which you could then test with something like:
declare
l_ref_cursor sys_refcursor;
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
drill_record_position('D', l_ref_cursor);
loop
fetch l_ref_cursor into l_id, l_group_name, l_group_level;
exit when l_ref_cursor%notfound;
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end loop;
close l_ref_cursor;
end;
/
You can also do that as a function, which might be easier to work with from your application:
-- drop procedure drill_record_position;
create or replace function drill_record_position (p_record_type in varchar2)
return sys_refcursor as
l_ref_cursor sys_refcursor;
begin
open l_ref_cursor for
select hr.id, hr.group_name, hr.group_level
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type;
return l_ref_cursor;
end drill_record_position;
/
declare
l_ref_cursor sys_refcursor;
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
l_ref_cursor := drill_record_position('D');
loop
fetch l_ref_cursor into l_id, l_group_name, l_group_level;
exit when l_ref_cursor%notfound;
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end loop;
close l_ref_cursor;
end;
/
You coudl also do this with collections and a pipelined function, which is more work to set up:
create type t_drill_obj as object (
-- use your real data types...
id number,
group_name varchar2(10),
group_level number
)
/
create type t_drill_tab as table of t_drill_obj
/
create or replace function drill_record_position (p_record_type in varchar2)
return t_drill_tab pipelined as
begin
for l_row in (
select t_drill_obj(hr.id, hr.group_name, hr.group_level) as obj
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type
)
loop
pipe row (l_row.obj);
end loop;
return;
end drill_record_position;
/
but you could call it as part of another query, and even join tot he result if you needed to:
select * from table(drill_record_position('D'));
I've got a PL/SQL package that returns a sys_refcursor based on the id that you pass it. I'd like to iterate through some ids and create a new ref cursor with one column from the original result set repeated for each id. (Sort of a cross tab.) A very simplified version of the PL/SQL block looks like:
create or replace package body dashboard_package is
procedure visits(RC in out sys_refcursor, IdNumber varchar2) as
BEGIN
OPEN RC FOR
select *
from (
select cat, cat_order, subcat, label_text
, trim(to_char(sum(v.current_month),'9,999,999,999')) current_month
, trim(to_char(sum(v.ly_month),'9,999,999,999')) ly_month
, trim(to_char(sum(v.ytd_tot),'9,999,999,999')) ytd_tot
, trim(to_char(sum(v.lytd_tot),'9,999,999,999')) lytd_tot
, trim(to_char(sum(v.ly_tot),'9,999,999,999')) ly_tot
from dashboard v
where v.id_number = IdNumber
group by cat_order, subcat, cat, label_text
union all
...
) a
order by cat_order, subcat;
END;
END;
I think if I had something like this
create or replace procedure test_refcur is
refCursorValue SYS_REFCURSOR;
begin
dashboard_package.visits(refCursorValue,12345);
for cursrow in refCursorValue loop
dbms_output.put_line(cursrow.ytd_tot);
end loop;
end test_refcur;
working, I could take it from there... any thoughts? Or perhaps clarification on the question that I should be asking.
If you're coming in with a number of IDs, then first prize would be to run only one SQL query to fetch the lot in one go, using a bulk in-bind for the IDs. This would probably require a modification to dashboard_package.visits, or writing a new version of the visits procedure to accept a PL/SQL table of IDs instead of a single ID.
If your hands are tied WRT modifying dashboard_package, then you could write a pipelined function to return the rows for a set of IDs:
-- create some helper types for the pipelined function
create type visitobj as object
(id number
,cat dashboard.cat%type
,cat_order dashboard.cat_order%type
,subcat dashboard.subcat%type
,label_text dashboard.label_text%type
,current_month varchar2(13)
,ly_month varchar2(13)
,ytd_tot varchar2(13)
,lytd_tot varchar2(13)
,ly_tot varchar2(13));
create type visittable as table of visitobj;
create or replace function test_refcur
return visittable deterministic pipelined is
refCursorValue SYS_REFCURSOR;
cat dashboard.cat%type;
cat_order dashboard.cat_order%type;
subcat dashboard.subcat%type;
label_text dashboard.label_text%type;
current_month varchar2(13);
ly_month varchar2(13);
ytd_tot varchar2(13);
lytd_tot varchar2(13);
ly_tot varchar2(13);
begin
for id in (/*iterate through the IDs*/) loop
dashboard_package.visits(refCursorValue, id);
loop
fetch refCursorValue into cat, cat_order, subcat, label_text,
current_month, ly_month, ytd_tot,
lytd_tot, ly_tot;
exit when refCursorValue%NOTFOUND;
pipe row (visitobj (id, cat, cat_order, subcat, label_text,
current_month, ly_month, ytd_tot,
lytd_tot, ly_tot));
end loop;
end loop;
return;
end test_refcur;
-- now you can simply do this:
SELECT * FROM TABLE(test_refcur);
(Of course, "/*iterate through the IDs*/" would be whatever method you want to use to gather the IDs for which the function should be called - e.g. could be a PL/SQL table of IDs, or perhaps another query).
Again I'd stress that "first prize" is to not do any of this extra work at all - just have a dashboard_package.visits that does it all in one SQL.
On a side note, trim(to_char(sum(v.ly_tot),'9,999,999,999')) can be simplified to to_char(sum(v.ly_tot),'FM9,999,999,999'). Also, if you use the format 'FM9G999G999G999' instead, it will be non-locale-specific.