How to get starting and ending date from last year in plsql - plsql

I have a plsql function count_order_cust with three parameters. But p_start and p_end are optional. If I call function with three parameters, then function is executed well, but if I call function with one parameter p_id, then it has to count orders for last year (i.e. 01/Jan/2013 - 31/Dec/2013) and this is my problem. How can I do this?
Here is function count_order_cust:
CREATE OR REPLACE FUNCTION count_order_cust(p_id f_customers.id%TYPE, p_sd f_orders.order_date%TYPE DEFAULT NULL, p_ed f_orders.order_date%TYPE DEFAULT NULL)
RETURN NUMBER IS v_count_orders NUMBER(3) := 0;
BEGIN
SELECT COUNT(cust_id) INTO v_count_orders
FROM f_orders
WHERE cust_id = p_id AND f_orders.order_date BETWEEN p_sd AND p_ed;
RETURN v_count_orders;
EXCEPTION WHEN NO_DATA_FOUND THEN RETURN NULL;
END count_order_cust;
With this select I get date in last year, but I need have in p_sd = '01/Jan/2013' and in p_ed = '31/Dec/2013'.
select add_months(sysdate,-12) from dual;

The below code shows you how to derive the required dates, then bundle the code into functions which can be used as the default parameters of your procedure:
declare
function getFirstDayOfLastYear return date is
begin
return trunc(add_months(sysdate,-12),'YEAR');
end;
function getLastDayOfLastYear return date is
begin
return trunc(sysdate,'YEAR') - 1;
end;
procedure myProc(
p_start date default getFirstDayOfLastYear,
p_end date default getLastDayOfLastYear
) is
begin
dbms_output.put_line(p_start);
dbms_output.put_line(p_end);
end;
begin
myProc;
end;

Related

Calling ref_cursor from another PLSQL function

I have three PLSQL functions: A, B and C.
The idea is this: C is calling B, B is calling A.
Function A, when it's called by B, returns a numeric value as a status indicator AND a ref cursor with tabular results.
E.g. function_A (A1 in varchar2, A2 out sys_refcursor) return number;
Function B, when it receives the results from A, is expected to reformat the results before passes them on to C, also in a form of a ref cursor.
A is an existing function and it cannot be amended, while B and C will be completely new functions.
The question is, how do I fetch the ref cursor from A? I was able to get the numeric value returned by the function (i.e the status indicator), but I have problem fetching the results of the ref cursor from A.
If I'm calling A from B, can I assume that the ref cursor of A is automatically opened?
What are the logical steps to get the results from A's ref cursor? E.g. can I fetch the results into an object type?
P/S. I have very limited programming experience and am only few months new in PLSQL.
Any hints will be much appreciated.
Since you have not given us the code functions, we will be based on the description of your functions.
According to the description, you have 3 functions:
Function A.
create or replace function A(A1 in varchar2, A2 out sys_refcursor) return number is
begin
open A2 for select 1 from dual;
return 2;
end;
Function B.
create or replace function B(B1 out sys_refcursor) return number is
cur sys_refcursor;
res_A number;
row_ your_table_a%rowtype;
begin
res_A := A('',cur);
loop
fetch cur into row_;
exit when cur%notfound;
--proccess with row A
end loop;
open B1 for select 2 from dual;
return 1;
end;
Function C
create or replace function C() return number is
res_B number;
cur sys_refcursor;
row_ your_table_b%rowtype;
begin
res_B:= B(cur);
loop
fetch cur into row_;
exit when cur%notfound;
--proccess with row B
end loop;
return 2;
end;
Maybe you can try the below snippet. Tried to replicate the scenario you mentioned in the question. Hope this helps.
CREATE OR REPLACE FUNCTION A_TEST(
A1 IN VARCHAR2,
A2 OUT sys_refcursor )
RETURN NUMBER
AS
lv_num PLS_INTEGER;
BEGIN
NULL;
OPEN a2 FOR SELECT LEVEL FROM DUAL CONNECT BY LEVEL < 19;
RETURN 1;
END;
CREATE OR REPLACE FUNCTION B_TEST
RETURN sys_refcursor
AS
lv_cur sys_refcursor;
lv_num PLS_INTEGER;
BEGIN
lv_num:=A_TEST('AV',lv_cur);
RETURN lv_cur;
END;
CREATE OR REPLACE FUNCTION C_TEST
RETURN sys_refcursor
AS
tab PLS_INTEGER;
lv_cur sys_refcursor;
BEGIN
lv_cur:=B_TEST;
LOOP
FETCH lv_cur INTO tab;
EXIT
WHEN lv_cur%NOTFOUND;
dbms_output.put_line(tab);
END LOOP;
END;

Bulding a fix length string with values passed using the length/position in pl/sql

I am writing a function which would build a fix a length empty string with value in a fix length string. for e.g.
I have a string of length 1000. When I pass string 'ABC' of length 5 with position 50, the function puts 'ABC' at 50th position of that fixed length string and pads 2 char with ' '.
I have managed to write it using regexp_replace. However I need it which doesnt use regexp_replace as it seems very slow in processing.
This function will be called in batch processing to build message string to be passed to other interface.
create or replace function insert_string(i_value varchar2, i_length number, i_position number) return varchar2
is
fix_string varchar2(1000) := ' ';
begin
fix_string := rpad(fix_string,1000,' ');
fix_string := regexp_replace(fix_string,'.{'||i_length ||'}',rpad(i_value,i_length,' '),i_position,1);
return fix_string;
end;
Something like this:
CREATE OR REPLACE FUNCTION insert_string(i_value VARCHAR2, i_length NUMBER, i_position NUMBER) RETURN VARCHAR2 IS
BEGIN
RETURN RPAD(LPAD(i_value, i_position + LENGTH(i_value) - 1), i_length);
END;
I dont know if this is what youve been trying to do/say but in case it is, ive added another parameter which will contain the source string. Without the string inside the function, its hard to manipulate values.
CREATE OR REPLACE FUNCTION INSERT_STRING(source_string VARCHAR2,
input_string VARCHAR2,
start_position NUMBER,
input_len NUMBER)
RETURN VARCHAR2 AS
TYPE char_arr IS TABLE OF CHAR(1) INDEX BY pls_integer;
TYPE char_arr2 IS TABLE OF CHAR(1);
source_array char_arr2:= char_arr2();
input_array char_arr;
X NUMBER:=1;
new_string VARCHAR2(2000);
BEGIN
FOR i IN 1..LENGTH(source_string) -- converts the source string to array
LOOP
source_array.extend;
source_array(i) := substr( source_string, i, 1 );
END LOOP;
-------------------------------------------------------------------
FOR j IN 1 .. input_len -- converts the input string to array with size input_len
LOOP
input_array(j) := substr( input_string, j, 1 );
END LOOP;
-------------------------------------------------------------------
--dbms_output.put_line(input_array(1));
FOR k IN 1..LENGTH(source_string) -- loop to insert the input_string to the source string
LOOP
IF k >= start_position THEN
source_array.extend;
source_array(k) := input_array(X) ;
x := x+1;
IF x > input_array.count THEN
exit;
end if;
END IF;
END LOOP;
FOR m IN 1 .. LENGTH(source_string) --loop to convert array back to string
LOOP
IF source_array(m) is null THEN
source_array(m) := ' ';
END IF;
new_string := new_string||source_array(m);
END LOOP;
RETURN new_string;
END;
Sample:
select insert_string('**********', 'ABC', 3, 5) from dual;
Output:
INSERT_STRING('**********','ABC',3,5)
**ABC ***
take note.
Source string should not be empty.
the size of the source string should be greater than the size of the input_string else the excess character from the input will not be inserted in the source string

ORA-21700 on SELECT FROM package-user-defined TABLE

I'm creating an Oracle package (MyPackage) where I have a public custom type table (ObjDataCollection) of custom type records (ObjData), which will be used as IN parameter for one of the Package functions (Calculate):
CREATE OR REPLACE PACKAGE MyPackage AS
TYPE ObjData IS RECORD (
t date NOT NULL := DATE '0001-01-01',
v number(9, 4)
);
TYPE ObjDataCollection IS TABLE
OF ObjData;
FUNCTION Calculate(
DataSource IN ObjDataCollection
) RETURN number;
END MyPackage;
CREATE OR REPLACE PACKAGE BODY MyPackage AS
FUNCTION Calculate(
DataSource IN ObjDataCollection
) RETURN number IS
res number(9, 4) := 0;
dateStart date;
dsv ObjData;
CURSOR q1 (dt date) IS
SELECT * FROM TABLE(DataSource) --Throws ORA-21700: Object does not exist or is marked for delete oracle.
WHERE t >= dt
ORDER BY t;
BEGIN
-- some irrelevant code
dateStart := DATE '2015-01-01';
OPEN q1(dateStart);
LOOP FETCH q1 INTO dsv;
EXIT WHEN q1%NOTFOUND;
res := res + dsv.v;
-- some irrelevant code
END LOOP;
CLOSE q1;
-- some irrelevant code
return res;
END Calculate;
END MyPackage;
I debbuged my code and I get the error on the second line of the cursor (marked in code):
ORA-21700: Object does not exist or is marked for delete oracle.
I'm using this data to execute my package:
CREATE TABLE TestTable (d date DEFAULT DATE '0001-01-01', v number(9, 4));
INSERT INTO TestTable VALUES (DATE '2015-01-01', 2.1);
INSERT INTO TestTable VALUES (DATE '2015-01-08', 3.1);
INSERT INTO TestTable VALUES (DATE '2015-01-15', 4.1);
INSERT INTO TestTable VALUES (DATE '2015-01-22', 5.1);
INSERT INTO TestTable VALUES (DATE '2015-01-29', 6.1);
INSERT INTO TestTable VALUES (DATE '2015-02-05', 7.1);
And this code to run a test:
CREATE OR REPLACE PROCEDURE TestMyPackage AS
res MyPackage.ObjDataCollection;
counter number(9, 4);
BEGIN
SELECT d, v
BULK COLLECT INTO res
FROM TestTable
ORDER BY v;
counter := MyPackage.Calculate(res);
END TestMyPackage;
Why I recieve this ORA-21700 exception?
PACKAGE BODY MyPackage AS
FUNCTION Calculate(
DataSource IN ObjDataCollection
) RETURN number IS
res BINARY_FLoAT:= 0;
dateStart date;
dsv ObjData;
copy_of_DataSource ObjDataCollection;
procedure sortCollection(toSort in out ObjDataCollection)
is
type idx_coll is table of ObjData;
type sort_help is table of idx_coll index by varchar2(16 char);
v_sort sort_help;
v_temp varchar2(16);
v_result ObjDataCollection := new ObjDataCollection();
v_cnt PLS_INTEGER := 0;
begin
for i in nvl(toSort.first,1) .. nvl(toSort.last,-1) loop
v_temp := to_char(toSort(i).t,'yyyymmddhh24miss');
if v_sort.exists(v_temp) then
v_sort(v_temp).extend(1);
v_sort(v_temp)(v_sort(v_temp).count) := toSort(i);
else
v_sort(v_temp) := idx_coll(toSort(i));
end if;
end loop;
v_result.extend(toSort.count);
v_temp := v_sort.first;
while v_temp is not null loop
for i in nvl(v_sort(v_temp).first,1) .. nvl(v_sort(v_temp).last,-1) loop
v_cnt := v_cnt +1;
v_result(v_cnt) := v_sort(v_temp)(i);
end loop;
v_temp := v_sort.next(v_temp);
end loop;
toSort := v_result;
end;
BEGIN
-- some irrelevant code
copy_of_DataSource := DataSource;
dateStart := DATE '2015-01-01';
sortCollection(copy_of_DataSource);
for i in nvl(copy_of_DataSource.first,1) .. nvl(copy_of_DataSource.last,-1) loop
if copy_of_DataSource(i).t > dateStart then
res := res + copy_of_DataSource(i).v;
dbms_output.put_line(copy_of_DataSource(i).t);
end if;
end loop;
-- some irrelevant code
return res;
END Calculate;
END MyPackage;
As it was a task due to a date, I ended up fixing it by creating user-defined types and tables at schema level as suggested Arkadiusz Ɓukasiewicz in one of his comments. Thank you for your effort.
As is not what I wanted, I won't mark any answer as Correct.
Got the same problem and solved it by doing the following steps:
Drop package where type is defined
Drop package using that type
Compile package with type
Compile target package
I got this ORA-21700 after adding new constants in a package with types/constants. I guess there's some internal issue with dependencies but I haven't dug a lot.

Get result set in plsql

am wondering if there already some rewriting suggestions to get functions such generating recurrence dates between two dates - generate_recurrences() from this link for recurrency recurrency events
in plsql? it returns a setof date, but in plsql i can't figure out how to get a resultset for dates and looping return next next_date, where next also returns a next date on a list.
I tried it to rewrite it in plsql but with only return of one date, because i can't find out how to return a resultset in plsql, that what i've tried:
CREATE OR REPLACE FUNCTION GENERATE_RECURRENCE( rec in VARCHAR2,
start_date in TIMESTAMP,
end_date in TIMESTAMP )
RETURN TIMESTAMP
IS
next_date TIMESTAMP := start_date;
duration INTERVAL DAY TO SECOND;
day INTERVAL DAY TO SECOND;
BEGIN
IF recurs = 'none' THEN
return next_date;
elsif recurs = 'daily' then
duration := INTERVAL '1' DAY ;
while next_date <= end_date loop
return next_date + duration;
END IF;
END;
I wrote the following pipelined function a while ago. It's not exactly what you're asking for, but it gives you a resultset that's a range of dates, so you should be able to match it to your needs.
It requires you to create a type object to hold the return value and I used an existing object instead of creating a custom one. So you should modify this to use an object just big enough (and use date type instead of string). But the functionality does what you're asking for.
Enjoy!
CREATE OR REPLACE FUNCTION date_range_stream(start_date_in IN DATE,
end_date_in IN DATE) RETURN rpt_results_10_obj_type_type
DETERMINISTIC
PIPELINED IS
/*
Parameters: start_date_in - First date to return (truncated)
end_date_in - Last date to return, inclusive
Results: date string formatted as MM/DD/YYYY
Author: Stew Stryker
Usage: SELECT to_date(text01, 'MM/DD/YYYY') AS a_date
FROM TABLE(aeo.aeo_misc_tools.date_range_stream('01-MAR-2009', SYSDATE))
Returns a rows from starting date to current
Requires the definition of the following object:
CREATE OR REPLACE TYPE rpt_results_10col_obj AS OBJECT
( seq_num NUMBER,
place VARCHAR2(20),
rep_info VARCHAR2(20),
text01 VARCHAR2(512),
text02 VARCHAR2(512),
text03 VARCHAR2(512),
text04 VARCHAR2(512),
text05 VARCHAR2(512),
text06 VARCHAR2(512),
text07 VARCHAR2(512),
text08 VARCHAR2(512),
text09 VARCHAR2(512),
text10 VARCHAR2(512));
*/
cur_date DATE := trunc(start_date_in);
date_row rpt_results_10col_obj := aeo.rpt_results_10col_obj(NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL);
BEGIN
WHILE cur_date <= trunc(end_date_in)
LOOP
date_row.text01 := TO_CHAR(cur_date, 'MM/DD/YYYY');
PIPE ROW(date_row);
cur_date := cur_date + 1;
END LOOP;
RETURN;
EXCEPTION
WHEN no_data_found THEN
RETURN;
WHEN OTHERS THEN
dbms_output.put_line('EXCEPTION IN aeo.aeo_misc_tools.date_range_stream - ' || SQLCODE || ': ' ||
SQLERRM);
RAISE;
RETURN;
END date_range_stream;

Not able to store values to a nested table in oracle

I am trying to write a function to show values for monthly data according to the selection made by the user in monthly report. Code snippet below is just trying to fetch values in a nested table and once data is loaded successfully in a nested table, I will call the function to display the table. I have tried a few things; but am running into issues while loading data. Below are 2 different SQLs to create this function but both of them are getting same error regarding incorrect values; I have tried a few things but to no avail:
Snippet 1:
/* Formatted on 10/16/2012 8:40:45 AM (QP5 v5.215.12089.38647) */
CREATE OR REPLACE TYPE tempObject AS OBJECT
(
kpiid number,
kpigroup VARCHAR2 (300)
);
CREATE OR REPLACE TYPE tempTable AS TABLE OF tempObject;
CREATE OR REPLACE FUNCTION KPI_HORIZON.Monthly_All_Data (
mainarea IN VARCHAR2)
RETURN tempTable
IS
MonthlyData temptable := temptable ();
n INTEGER := 0;
BEGIN
IF (mainarea = 'ALL')
THEN
FOR r IN (SELECT DISTINCT kpiid, kpigroup
FROM kpi_summary_reporting
WHERE kpifrequency = 'Monthly' AND active_ind = 'Y')
LOOP
monthlydata.EXTEND;
n := n + 1;
monthlydata (n) := tempobject (r.kpiid, r.kpigroup);
END LOOP;
END IF;
RETURN MonthlyData;
END;
Error: [Error] PLS-00306 (26: 29): PLS-00306: wrong number or types of arguments in call to 'TEMPOBJECT'
Snippet2:
/* Formatted on 10/16/2012 8:27:22 AM (QP5 v5.215.12089.38647) */
CREATE OR REPLACE TYPE tempObject AS OBJECT
(
kpiid NUMBER,
kpigroup VARCHAR2 (300)
);
CREATE OR REPLACE TYPE tempTable AS TABLE OF tempObject;
CREATE OR REPLACE FUNCTION KPI_HORIZON.Monthly_All_Data (
mainarea IN VARCHAR2)
RETURN tempTable
AS
MonthlyData temptable := temptable ();
BEGIN
IF (mainarea = 'ALL')
THEN
SELECT DISTINCT ksr.kpiid, ksr.kpigroup
INTO MonthlyData
FROM kpi_summary_reporting ksr
WHERE kpifrequency = 'Monthly' AND active_ind = 'Y';
ELSE
SELECT DISTINCT kpiid, kpigroup
INTO MonthlyData
FROM kpi_summary_reporting;
END IF;
RETURN MonthlyData;
END;
Error: [Error] ORA-00947 (24: 9): PL/SQL: ORA-00947: not enough values
I would do something like this assuming that the data is small enough that it really makes sense to load it entirely into a nested table in the server's PGA. If the data volume is larger, you probably want to use a pipelined table function instead.
Since your nested table is a table of object types, you need to use the object type constructor.
CREATE OR REPLACE FUNCTION KPI_HORIZON.Monthly_All_Data (
mainarea IN VARCHAR2)
RETURN tempTable
IS
MonthlyData temptable;
BEGIN
IF (mainarea = 'ALL')
THEN
SELECT tempObject( kpiid, kpigroup )
BULK COLLECT INTO monthlydata
FROM kpi_summary_reporting
WHERE kpifrequency = 'Monthly'
AND active_ind = 'Y';
END IF;
RETURN MonthlyData;
END;
I'm always dubious when I see a DISTINCT in a query. Do you really expect to get duplicate rows that you need to remove? If not, you'll be much better served removing the DISTINCT as I did above. If you really need the DISTINCT, then your object type would need a MAP or an ORDER method which would complicate the example a bit.
A demonstration of this working
SQL> CREATE OR REPLACE TYPE tempObject AS OBJECT
2 (
3 kpiid NUMBER,
4 kpigroup VARCHAR2 (300)
5 );
6 /
Type created.
SQL> CREATE OR REPLACE TYPE tempTable AS TABLE OF tempObject;
2 /
Type created.
SQL> create table kpi_summary_reporting (
2 kpiid integer,
3 kpigroup varchar2(300),
4 kpifrequency varchar2(30),
5 active_ind varchar2(1)
6 );
Table created.
SQL> insert into kpi_summary_reporting values( 1, 'Foo', 'Monthly', 'Y' );
1 row created.
SQL> ed
Wrote file afiedt.buf
1 CREATE OR REPLACE FUNCTION Monthly_All_Data (
2 mainarea IN VARCHAR2)
3 RETURN tempTable
4 IS
5 MonthlyData temptable;
6 BEGIN
7 IF (mainarea = 'ALL')
8 THEN
9 SELECT tempObject( kpiid, kpigroup )
10 BULK COLLECT INTO monthlydata
11 FROM kpi_summary_reporting
12 WHERE kpifrequency = 'Monthly'
13 AND active_ind = 'Y';
14 END IF;
15 RETURN MonthlyData;
16* END;
17 /
Function created.
SQL> select monthly_all_data( 'ALL' ) from dual;
MONTHLY_ALL_DATA('ALL')(KPIID, KPIGROUP)
--------------------------------------------------------------------------------
TEMPTABLE(TEMPOBJECT(1, 'Foo'))

Resources