pipelined function with if statement - oracle

I'd like to create a function to retrieve valid years for each record. I've got columns valid_from and valid_to in table.
For example valid_from = 2018-04-30 and valid_to = 2020-06-30; function should result three values: 2018, 2019, 2020.
I created a type and a function but it isn't working at all ...
create or replace type t_years is table of varchar2(4);
create or replace function func_year(d_from date, d_to date) return t_years pipelined
is
v_date date := '21/12/31 00:00:00';
max_date date := '99/12/30 00:00:00';
begin
for x in 1..2 loop
if d_from <= add_months(v_date,-1*12) AND (add_months(d_to,-1*12) >= v_date OR add_months(d_to,-1*12) = max_date) then return '2020';
elsif d_from <= add_months(v_date,-2*12) AND (add_months(d_to,-2*12) >= v_date OR add_months(d_to,-2*12) = max_date) then return '2019';
end if;
pipe row(x);
end loop;
return;
end;

Here's how:
SQL> CREATE OR REPLACE TYPE t_years IS TABLE OF VARCHAR2 (4);
2 /
Type created.
SQL> CREATE OR REPLACE FUNCTION func_year (d_from IN DATE, d_to IN DATE)
2 RETURN t_years
3 PIPELINED
4 IS
5 BEGIN
6 FOR i IN EXTRACT (YEAR FROM d_from) .. EXTRACT (YEAR FROM d_to)
7 LOOP
8 PIPE ROW (i);
9 END LOOP;
10
11 RETURN;
12 END;
13 /
Function created.
SQL> SELECT func_year (DATE '2019-01-01', DATE '2021-01-01') FROM DUAL;
FUNC_YEAR(DATE'2019-01-01',DATE'2021-01-01')
----------------------------------------------------------------------------------------------------
T_YEARS('2019', '2020', '2021')
SQL>
Or
SQL> SELECT * FROM TABLE (func_year (DATE '2019-01-01', DATE '2021-01-01'));
COLU
----
2019
2020
2021
SQL>

Related

I want to Create a function named VALIDATE_EMP which accepts employeeNumber as a parameter, Returns TRUE or FALSE depending on existence

Sample Data
create table Employees (emp_id number, emp_name varchar2(50), salary number, department_id number) ;
insert into Employees values(1,'ALex',10000,10);
insert into Employees values(2,'Duplex',20000,20);
insert into Employees values(3,'Charles',30000,30);
insert into Employees values(4,'Demon',40000,40);
Code :
create or replace function validate_emp(empno in number)
return boolean
is lv_count number
begin
select count(employee_id) into lv_count from hr.employees where employee_id = empno;
if lv_count >1 then
return true;
else
return false;
end;
I want to Create a function named VALIDATE_EMP which accepts empno as a parameter, Returns TRUE if the specified employee exists in the table name “Employeee” else FALSE.
missing semi-colon as terminator of the local variable declaration
if user you're connected to isn't hr, remove it (otherwise, leave it as is)
column name is emp_id, not employee_id
missing end if
When fixed, code compiles:
SQL> CREATE OR REPLACE FUNCTION validate_emp (empno IN NUMBER)
2 RETURN BOOLEAN
3 IS
4 lv_count NUMBER;
5 BEGIN
6 SELECT COUNT (emp_id)
7 INTO lv_count
8 FROM employees
9 WHERE emp_id = empno;
10
11 IF lv_count > 1
12 THEN
13 RETURN TRUE;
14 ELSE
15 RETURN FALSE;
16 END IF;
17 END;
18 /
Function created.
SQL>
How to call it? Via PL/SQL as Oracle's SQL doesn't have the Boolean datatype.
SQL> set serveroutput on
SQL> declare
2 result boolean;
3 begin
4 result := validate_emp(1);
5
6 dbms_output.put_line(case when result then 'employee exists'
7 else 'employee does not exist'
8 end);
9 end;
10 /
employee does not exist
PL/SQL procedure successfully completed.
SQL>
Maybe you'd rather return VARCHAR2; then you'd mimic Boolean, but you'd be able to use it in plain SQL:
SQL> CREATE OR REPLACE FUNCTION validate_emp (empno IN NUMBER)
2 RETURN VARCHAR2
3 IS
4 lv_count NUMBER;
5 BEGIN
6 SELECT COUNT (emp_id)
7 INTO lv_count
8 FROM employees
9 WHERE emp_id = empno;
10
11 IF lv_count > 1
12 THEN
13 RETURN 'TRUE';
14 ELSE
15 RETURN 'FALSE';
16 END IF;
17 END;
18 /
Function created.
SQL> select validate_emp(1) from dual;
VALIDATE_EMP(1)
-------------------------------------------------------------------
FALSE
SQL>

oracle loop over date

I am having difficulties setting up a loop in Oracle. I have a table where values are stored for several days. Now I want to get the average of these values for each day.
I was attempting to set up a loop like this:
DECLARE
BEGIN
For iDay in 01.03.20, 02.03.20, 03.03.20
LOOP
SELECT
avg(values)
FROM
table
WHERE
date = 'iDay'
END LOOP;
END
You can simply get the average value using the following query:
SELECT DATE,
AVG (values)
FROM table
WHERE DATE BETWEEN DATE '2020-03-01' AND DATE '2020-03-03';
Or if you want to use the loop then use the query in FOR loop IN clause as follows:
SQL> DECLARE
2 BEGIN
3 FOR DATAS IN (
4 SELECT DATE '2020-03-01' + LEVEL - 1 DT
5 FROM DUAL CONNECT BY
6 LEVEL <= DATE '2020-03-03' - DATE '2020-03-01' + 1
7 ) LOOP
8 DBMS_OUTPUT.PUT_LINE(DATAS.DT);
9 -- YOUR_CODE_HERE
10 END LOOP;
11 END;
12 /
01-MAR-20
02-MAR-20
03-MAR-20
PL/SQL procedure successfully completed.
SQL>
One option would be using Dynamic Query within PL/SQL :
SQL> SET SERVEROUTPUT ON;
SQL> DECLARE
v_result NUMBER;
BEGIN
For iDay in 0..2
LOOP
EXECUTE IMMEDIATE 'SELECT AVG(values) FROM mytable WHERE mydate = :i_Day '
INTO v_result;
USING iDay + date'2020-03-01';
DBMS_OUTPUT.PUT_LINE( v_result );
END LOOP;
END;
/
Why not simply
SELECT "date", avg(values)
FROM "table"
WHERE "date" between DATE '2020-03-01' and DATE '2020-03-03'
GROUP by "date";
Note, date and table are reserved words, most likely the query will without quotes.

converting a plsql code into a function

I have this piece of code which I want to convert into a function , and call the function in a select statement by passing vendor_site_id from ap_supplier_sites_all. so that I can get the value of vendor_number i.e segment1 of ap_suppliers . here in this piece of code , I have a login to fetch first 6 digits from the column attribute52 of DFF table gecm_dff_ext .
DECLARE
v_ret_val VARCHAR2(30);
v_sup_gsl VARCHAR2(30);
v_vendor_id NUMBER;
BEGIN
v_vendor_id := '${PO.H_VENDOR_ID}';--> vendor_id column from ap_suppliers
IF <condition> = 'Y'
THEN
BEGIN
SELECT SUBSTR(ATTRIBUTE52,1,6)
INTO v_sup_gsl
FROM gecm_dff_ext
WHERE primary_table ='AP_SUPPLIER_SITES_ALL'
AND primary_key = '${PO.H_VENDOR_SITE_ID}';--> This value should be vendor_site_id from ap_supplier_sites_all table
EXCEPTION
WHEN OTHERS THEN
v_sup_gsl := NULL;
END;
BEGIN
IF v_sup_gsl IS NOT NULL THEN
SELECT segment1
INTO v_ret_val
FROM ap_suppliers
WHERE segment1 = v_sup_gsl;
END IF;
EXCEPTION
WHEN OTHERS THEN
v_ret_val := '';
END;
END IF;
IF v_sup_gsl IS NULL THEN
BEGIN
SELECT SEGMENT1
INTO v_ret_val
FROM ap_suppliers
WHERE vendor_id=v_vendor_id;
EXCEPTION
WHEN OTHERS THEN
v_ret_val := '';
END;
END IF;
:return_value:=v_ret_val;
END;
This is a simplified example: the following SELECT returns department name for a certain department number:
SQL> select dname
2 from dept
3 where deptno = &par_deptno;
Enter value for par_deptno: 10
DNAME
--------------
ACCOUNTING
SQL>
So, how to convert it to a function? By using a proper syntax, declaring a return variable, fetching into it and - returning the result:
SQL> create or replace function f_test (par_deptno in dept.deptno%type)
2 return dept.dname%type
3 is
4 retval dept.dname%type;
5 begin
6 select dname
7 into retval
8 from dept
9 where deptno = par_deptno;
10
11 return retval;
12 exception
13 when no_data_found then
14 return null;
15 end;
16 /
Function created.
SQL> select f_test(10) from dual;
F_TEST(10)
---------------------------------------------------------------------------
ACCOUNTING
SQL> select f_test(999) from dual;
F_TEST(999)
---------------------------------------------------------------------------
SQL>
This is what you should do. It seems that you are returning the v_ret_val, so - your code might look like this:
create or replace function f_test (par_vendor_id po.h_vendor_id%type)
return ap_suppliers.segment1%type
is
v_ret_val ap_suppliers.segment1%type;
begin
if <condition> = 'Y' then
...
end if;
return v_ret_val;
end;

How to get n element from nested table pl/sql?

I have problem with the compilation of my stored procedure.
create or replace type CartLine as object (
offeringId OfferingIdList
,productLine varchar2(50)
,equipment char(1)
,installment CHAR(1)
,cartItemProcess varchar2(50)
,minimalPrice decimal
);
create or replace type CartLineType is table of CartLine;
create or replace PROCEDURE GetOfferingRecommendation (
cartLineList IN CartLineType,
user IN UserType,
customer IN CustomerType,
processContext IN ProcessContextType,
recommendation out SYS_REFCURSOR )
IS
prodLine VARCHAR2(20);
prodPrice NUMBER(5,0);
BEGIN
FOR i IN cartLineList.FIRST .. cartLineList.LAST
LOOP
SELECT productLine, minimalPrice
INTO prodLine, prodPrice
FROM TABLE(cartLineList(i));
OPEN recommendation FOR
SELECT CAST(REKOM_ID_SEQ.NEXTVAL AS VARCHAR(10))
||'_'||cp.ID_REKOM_OFERTA
||'_'||TO_CHAR(SYSDATE, 'yyyymmdd') AS recommendationId
,cp.ID_REKOM_OFERTA AS offeringId
,cp.PRIORYTET AS priority
FROM REKOM_CROSS_PROM cp
WHERE cp.LINIA_PROD = prodLine
AND prodPrice BETWEEN cp.CENA_MIN AND cp.CENA_MAX
;
END LOOP;
END GetOfferingRecommendation;
It is not getting compiled cause the following statement is wrong:
SELECT productLine, minimalPrice
INTO prodLine, prodPrice
FROM TABLE(cartLineList(i));
I want to select only single value every all new iteration of my loop.
Can somebody help me to resolve my problem?
-- EDIT 1/9/2018 4:26 PM
According to topic:
How to return result of many select statements as one custom table
I tried to rebuild my procedure.
I created types for test:
create or replace TYPE tst AS OBJECT (
rekom_id varchar2(50)
,rekom_priorytet number(5,4)
);
/
create or replace TYPE tst_list IS TABLE OF tst;
After that, I changed my procedure like below:
CREATE OR REPLACE PROCEDURE GetOfferingRecommendation (cartLineList IN CartLineType, recommendation out SYS_REFCURSOR )
IS
CURSOR CUR_TAB IS SELECT productLine, minimalPrice FROM TABLE(cartLineList);
v_tst tst_list;
BEGIN
FOR i IN CUR_TAB
LOOP
EXECUTE IMMEDIATE 'SELECT tst_list(
CAST(REKOM_ID_SEQ.NEXTVAL AS VARCHAR(10))||''_''||cp.ID_REKOM_OFERTA||''_''||TO_CHAR(SYSDATE, ''yyyymmdd'')
,cp.PRIORYTET)
FROM REKOM_CROSS_PROM cp
WHERE cp.LINIA_PROD ='||i.productLine||' AND '||i.minimalPrice||' BETWEEN cp.CENA_MIN AND cp.CENA_MAX'
BULK COLLECT INTO v_tst;
EXIT WHEN CUR_TAB%NOTFOUND;
FOR REC IN 1 .. v_tst.COUNT
LOOP
PIPE ROW (v_tst(REC));
END LOOP;
END LOOP;
OPEN recommendation FOR SELECT * FROM TABLE(v_tst);
END IF;
END GetOfferingRecommendation;
But I can't compile because error occured: PLS-00629
Would you please told me what I do wrong?
You cannot assign variables using a select statement from a collection in a loop like below.
SELECT productLine, minimalPrice
INTO prodLine, prodPrice
FROM TABLE(cartLineList(i));
The collection elements cannot be referred inside a SELECT statement 1 by 1 using a loop. You can loop through the collection as
For i in 1..collection.count
loop
...
..
End loop;
Collection has a number of rows and when you do so, you try to assign many rows to a single variable, which is wrong. You can do either of the below explained. There relevant explanation is inline.
CREATE OR REPLACE PROCEDURE GETOFFERINGRECOMMENDATION (
CARTLINELIST IN CARTLINETYPE,
RECOMMENDATION OUT SYS_REFCURSOR)
IS
TYPE V_PRODLINE IS TABLE OF VARCHAR2 (20)
INDEX BY PLS_INTEGER;
TYPE V_PRODPRICE IS TABLE OF NUMBER (5, 0)
INDEX BY PLS_INTEGER;
PRODLINE V_PRODLINE;
PRODPRICE V_PRODPRICE;
BEGIN
--Putting the collection result to another collection
SELECT PRODUCTLINE,
MINIMALPRICE
BULK COLLECT INTO PRODLINE,
PRODPRICE
FROM TABLE (CARTLINELIST);
-- Assuming number of elements will be same in both prodLine, prodPrice colection, loop can be iterated as below
FOR I IN 1 .. PRODLINE.LAST
LOOP
OPEN RECOMMENDATION FOR
SELECT CAST (REKOM_ID_SEQ.NEXTVAL AS VARCHAR (10) )
|| '_'
|| CP.ID_REKOM_OFERTA
|| '_'
|| TO_CHAR (SYSDATE, 'yyyymmdd') AS RECOMMENDATIONID,
CP.ID_REKOM_OFERTA AS OFFERINGID,
CP.PRIORYTET AS PRIORITY
FROM REKOM_CROSS_PROM CP
WHERE CP.LINIA_PROD = PRODLINE (I)
AND PRODPRICE (I) BETWEEN CP.CENA_MIN AND CP.CENA_MAX;
END LOOP;
END GETOFFERINGRECOMMENDATION;
OR as per #krokodilko.. You can do as below:
CREATE OR REPLACE PROCEDURE GETOFFERINGRECOMMENDATION (
CARTLINELIST IN CARTLINETYPE,
RECOMMENDATION OUT SYS_REFCURSOR)
IS
PRODLINE VARCHAR2 (20);
PRODPRICE NUMBER (5, 0);
BEGIN
FOR I IN 1 .. CARTLINELIST.LAST
LOOP
--Assign the values of the collection to the variable declared.
PRODUCTLINE := CARTLINELIST (I).PRODUCTLINE;
MINIMALPRICE := CARTLINELIST (I).MINIMALPRICE;
OPEN RECOMMENDATION FOR
SELECT CAST (REKOM_ID_SEQ.NEXTVAL AS VARCHAR (10) )
|| '_'
|| CP.ID_REKOM_OFERTA
|| '_'
|| TO_CHAR (SYSDATE, 'yyyymmdd') AS RECOMMENDATIONID,
CP.ID_REKOM_OFERTA AS OFFERINGID,
CP.PRIORYTET AS PRIORITY
FROM REKOM_CROSS_PROM CP
WHERE CP.LINIA_PROD = PRODLINE
AND PRODPRICE BETWEEN CP.CENA_MIN AND CP.CENA_MAX;
END LOOP;
END GETOFFERINGRECOMMENDATION;
Demo:
SQL> CREATE OR REPLACE TYPE CARTLINE AS OBJECT (
2 PRODUCTLINE VARCHAR2 (50),
3 MINIMALPRICE DECIMAL
4 );
5 /
Type created.
SQL> CREATE OR REPLACE TYPE CARTLINETYPE IS TABLE OF CARTLINE;
2 /
Type created.
SQL> CREATE OR REPLACE PROCEDURE GETOFFERINGRECOMMENDATION (
2 CARTLINELIST IN CARTLINETYPE)
3 IS
4 TYPE V_PRODLINE IS TABLE OF VARCHAR2 (20)
5 INDEX BY PLS_INTEGER;
6
7 TYPE V_PRODPRICE IS TABLE OF NUMBER (5, 0)
8 INDEX BY PLS_INTEGER;
9
10 PRODLINE V_PRODLINE;
11 PRODPRICE V_PRODPRICE;
12 BEGIN
13 SELECT PRODUCTLINE,
14 MINIMALPRICE
15 BULK COLLECT INTO PRODLINE,
16 PRODPRICE
17 FROM TABLE (CARTLINELIST);
18
19 FOR I IN 1 .. PRODLINE.COUNT
20 LOOP
21 DBMS_OUTPUT.PUT_LINE ( 'Prod Line '
22 || PRODLINE (I)
23 || ' Prod Price '
24 || PRODPRICE (I) );
25 END LOOP;
26 END GETOFFERINGRECOMMENDATION;
27 /
Procedure created.
Output:
SQL> DECLARE
2 VAR CARTLINETYPE := CARTLINETYPE ();
3 BEGIN
4 --Popuating the collection
5 VAR.EXTEND (2);
6 VAR (1) := CARTLINE ('TypeA', 6.0);
7 VAR (2) := CARTLINE ('TypeB', 7.1);
8
9 --Calling the procedure
10 GETOFFERINGRECOMMENDATION (CARTLINELIST => VAR);
11 END;
12 /
Prod Line TypeA Prod Price 6
Prod Line TypeB Prod Price 7
PL/SQL procedure successfully completed.
SQL>
Use simple assignments instead of SELECT ... FROM TABLE(cartLineList(i));:
LOOP
/* SELECT productLine, minimalPrice INTO prodLine, prodPrice FROM TABLE(cartLineList(i)); */
productLine := cartLineList(i).productLine;
minimalPrice := cartLineList(i).minimalPrice;
.....
.....
END LOOP;

How to pass current month and year in input parameters in pl sql?

I'm trying to pass the month and year in a plsql as input parameters. I want the month and year to be the default values as of current month and current year if i'm passing null values.
A DATE should be passed as a date and not in parts. You could EXTRACT the requirement elements as and when required.
Pass the SYSDATE as DEFAULT IN parameter:
p_date DATE default SYSDATE
And then EXTRACT the elements:
For example,
SQL> SELECT SYSDATE ,
2 EXTRACT(YEAR FROM SYSDATE) AS curr_year ,
3 EXTRACT(MONTH FROM SYSDATE) AS curr_month ,
4 EXTRACT(DAY FROM SYSDATE) AS curr_day
5 FROM dual;
SYSDATE CURR_YEAR CURR_MONTH CURR_DAY
--------- ---------- ---------- ----------
03-NOV-15 2015 11 3
Depending on your requirement, you could use TO_CHAR, however, the data type of the result will be string.
For example,
SQL> SELECT SYSDATE ,
2 TO_CHAR(SYSDATE, 'YYYY') AS curr_year ,
3 TO_CHAR(SYSDATE, 'MM') AS curr_month ,
4 TO_CHAR(SYSDATE, 'DD') AS curr_day
5 FROM dual;
SYSDATE CURR_YEAR CURR_MONTH CURR_DAY
--------- --------- ---------- --------
03-NOV-15 2015 11 03
First variant -
CREATE OR REPLACE PROCEDURE test_poc(
year_num IN NUMBER DEFAULT to_number(to_char(sysdate, 'yyyy')),
month_num IN NUMBER DEFAULT to_number(to_char(sysdate, 'mm'))) IS
BEGIN
....
END;
Second (is better, with a date in input parameter) -
CREATE OR REPLACE PROCEDURE test_poc(date_in IN DATE) IS
year_num NUMBER;
month_num NUMBER;
BEGIN
year_num := to_number(to_char(nvl(date_in, sysdate), 'yyyy'));
month_num := to_number(to_char(nvl(date_in, sysdate), 'mm'));
...
END;
You can define a default for your parameters as suggested in the other answers. But those default values will only take effect when you dont pass anything as parameter. Giving null as parameter value will not trigger the default value. For example
create or replace
function TEST_FN( p_month in number default extract( MONTH from sysdate ) )
return varchar2
as
begin
return 'month='||nvl( p_month, -1);
end;
A select TEST_FN() from dual; will result in month=11 for november, and selecting select TEST_FN(null) from dual; gives month=-1.
If you want to replace a null with some default value use nvl(...).
create or replace procedure p1(my_year int default null, my_month integer default null)
as
lyear int := nvl(my_year, to_char(sysdate,'yyyy'));
lmonth int := nvl(my_month, to_char(sysdate,'mm'));
begin
dbms_output.put_line(lyear);
dbms_output.put_line(lmonth);
end;
test
begin
p1;
p1(2010);
p1(null,12);
p1(2009,10);
end;
output
2015
11
2010
11
2015
12
2009
10

Resources