ORACLE SQL PROCEDURE WITH FOR LOOP AND IF ELSE ERROR - sql

I need to replace employee id in shift table for a particular day with another employee id who hasn't any shift for a particular day.
This my tables,
CREATE TABLE EMPLOYEE
(EMPLOYEE_ID NUMBER(4) NOT NULL,
NAME VARCHAR(50),
GENDER VARCHAR(10),
BIRTH_DAY DATE,
PRIMARY KEY(EMPLOYEE_ID)
);
CREATE TABLE SHIFT
(SHIFT_ID NUMBER(4),
SHIFT_DATE DATE,
CUSTOMER_ID NUMBER(4),
SERVICE_ID NUMBER(4),
EMPLOYEE_ID NUMBER(4),
SHIFT_CHARGE FLOAT(10),
PRIMARY KEY(SHIFT_ID),
FOREIGN KEY (EMPLOYEE_ID) REFERENCES EMPLOYEE(EMPLOYEE_ID)
);
For the above purpose, I want to create a procedure. I created the procedure is below. But there is a compilation error.
CREATE OR REPLACE PROCEDURE CHANGE_SHIFT_SHEDULED
(
EMPLOYEE_ID IN SHIFT.EMPLOYEE_ID%TYPE,
SHIFT_DATE IN SHIFT.SHIFT_DATE%TYPE
)
AS
CHECK_CON BOOLEAN:=TRUE;
BEGIN
FOR SICK_EMP_SHIFT_REC IN (SELECT * FROM SHIFT WHERE SHIFT.EMPLOYEE_ID=EMPLOYEE_ID AND SHIFT.SHIFT_DATE=SHIFT_DATE)
LOOP
FOR EMP_REC IN (
SELECT e.EMPLOYEE_ID,COUNT(e.EMPLOYEE_ID)AS SHIFT_COUNT,
CASE
WHEN SHIFT_COUNT>0 THEN FALSE
ELSE TRUE
END AS SHIFT_COUNT
FROM EMPLOYEE e LEFT OUTER JOIN SHIFT s ON e.EMPLOYEE_ID=s.EMPLOYEE_ID
WHERE s.SHIFT_DATE=SHIFT_DATE
GROUP BY e.EMPLOYEE_ID)
LOOP
CHECK_CON:=EMP_REC.SHIFT_COUNT;
IF CHECK_CON THEN
UPDATE SHIFT s SET s.EMPLOYEE_ID=EMP_REC.EMPLOYEE_ID WHERE s.SHIFT_ID=SICK_EMP_SHIFT_REC.SHIFT_ID;
EXIT;
END IF;
END LOOP;
END LOOP;
END;
/
Please anyone help me to solve this issue.

I'm not looking at logic you applied; I hope you did it right. I just fixed errors which prevented code you wrote to compile; for example:
don't name parameters as column names; use some prefix, e.g. par_ or p_
in cursor FOR loop, you can't have two columns with same name (shift_count); one has to be changed
also, you can't use an alias in the same statement's CASE (which is what you did with shift_count)
Boolean support is somewhat strange in Oracle. For example, there's no such datatype at SQL level. Therefore, you'll have to work with strings or numbers, and then "convert" them to Boolean (e.g. line #26)
The rest is OK, I suppose.
SQL> CREATE OR REPLACE PROCEDURE change_shift_sheduled (
2 par_employee_id IN shift.employee_id%TYPE,
3 par_shift_date IN shift.shift_date%TYPE)
4 AS
5 check_con BOOLEAN := TRUE;
6 BEGIN
7 FOR sick_emp_shift_rec IN (SELECT *
8 FROM shift
9 WHERE shift.employee_id = par_employee_id
10 AND shift.shift_date = par_shift_date)
11 LOOP
12 FOR emp_rec
13 IN ( SELECT e.employee_id,
14 COUNT (e.employee_id) AS shift_count,
15 CASE
16 WHEN COUNT (e.employee_id) > 0 THEN 'FALSE'
17 ELSE 'TRUE'
18 END
19 AS shift_count_case
20 FROM employee e
21 LEFT OUTER JOIN shift s ON e.employee_id = s.employee_id
22 WHERE s.shift_date = par_shift_date
23 GROUP BY e.employee_id)
24 LOOP
25 check_con :=
26 CASE
27 WHEN emp_rec.shift_count_case = 'FALSE' THEN FALSE
28 ELSE TRUE
29 END;
30
31 IF check_con
32 THEN
33 UPDATE shift s
34 SET s.employee_id = emp_rec.employee_id
35 WHERE s.shift_id = sick_emp_shift_rec.shift_id;
36
37 EXIT;
38 END IF;
39 END LOOP;
40 END LOOP;
41 END;
42 /
Procedure created.
SQL>

Related

Can somebody help me with these trigger errors in PL/SQL?

I have these tables:
Person (id_pers, name, phone, address, birthdate)
Book (book_id, title, nr_of_pages, copies, genre)
Loan(book_id, loan_id, dateL, dateR, nr_of_days)
birthdate is of datatype DATE;
loan_id takes value from the id_pers;
dateL - loan date;
dateR - return date;
nr_of_days represents the return term (how many days the loan may take);
Define a trigger for:
Ensure that when adding a loan, the nr_of_days is proportionate to
nr_of_pages taking into account the age of the person making the loan. Between 18-25 years,
30 pages/day; between 26-65 years, 20 pages/day; over 65 years, 40 pages/day.
I tried using this trigger but it compiles with errors
create or replace TRIGGER Update_Days
BEFORE INSERT ON Loan
FOR EACH ROW
BEGIN
DECLARE age INT;
SELECT DATEDIFF(YEAR, birthdate, CURRENT_DATE) INTO age
FROM Person
WHERE id_pers = NEW.loan_id;
IF age BETWEEN 18 AND 25 THEN
SET NEW.nr_of_days = NEW.nr_of_pages / 30;
ELSEIF age BETWEEN 26 AND 65 THEN
SET NEW.nr_of_days = NEW.nr_of_pages / 20;
ELSE
SET NEW.nr_of_days = NEW.nr_of_pages / 40;
END IF;
END;
errors i get
SET is not used in PL/SQL
In PL/SQL, the assignment operator is := and not =
The NEW record is referenced using bind variable syntax of :NEW
DATEDIFF is not a valid function in Oracle.
The DECLARE section goes before BEGIN.
CREATE OR REPLACE TRIGGER Update_Days
BEFORE INSERT ON loan
FOR EACH ROW
DECLARE
age INT;
BEGIN
SELECT TRUNC(MONTHS_BETWEEN(CURRENT_DATE, birthdate)/12)
INTO age
FROM Person
WHERE id_pers = :NEW.loan_id;
:NEW.nr_of_days := :NEW.nr_of_pages / CASE
WHEN age BETWEEN 18 AND 25 THEN 30
WHEN age BETWEEN 26 AND 65 THEN 20
ELSE 40
END;
END;
/
fiddle

WITH clause with FUNCTION AND PROCEDURE, where is the mistake?

I have table 'Studies' (with columns: student_id, name, surname, course_name, mark) and I have a task to write a PL/SQL program, where will be WITH word + FUNCTION word + PROCEDURE word.I decided to make such a program: the function will calculate the average mark for some course (input parameter) and the procedure will display information about students whose mark in this course is higher than the average. I managed to create a function that returns the average score,
create or replace FUNCTION average_mark(co_name IN VARCHAR2) RETURN REAL IS
iter NUMBER := 0;
aver NUMBER := 0;
CURSOR c1
IS
SELECT mark
FROM studies
WHERE course_name = co_name;
BEGIN
FOR student IN c1
LOOP
iter := iter + 1;
aver := aver + student.mark;
END LOOP;
RETURN ROUND((aver/iter),2);
END above_average_mark;
and procedure which displays information about a student whose mark in the course is more than a certain one, how now to connect the procedure and the function and the WITH word?
CREATE OR REPLACE PROCEDURE above(co_name IN VARCHAR2) IS
CURSOR c2
IS
SELECT *
FROM studies
WHERE course_name = co_name;
BEGIN
FOR student IN c2
LOOP
IF (student.mark > 4) THEN
DBMS_OUTPUT.PUT_LINE('name: ' || student.student_name || ', mark: ' || student.mark);
END IF;
END LOOP;
END;
i need something like this:
WITH
PROCEDURE
FUNCTION
I don't have your tables to illustrate it, so I'll use Scott's sample schema to calculate average salaries for departments.
As you said that you need something like WITH PROCEDURE FUNCTION, the only thing you have to do is to follow syntax.
Therefore, here you are: with factoring clause in this example contains a procedure which displays department name and average salary; function calls the procedure (and passes department number and average salary it calculated). Also, as any other function it actually returns a value.
SQL> set serveroutput on;
SQL> with
2 procedure p_deptno (par_deptno in dept.deptno%type,
3 par_avgsal in number)
4 is
5 l_dname dept.dname%type;
6 begin
7 select dname into l_dname
8 from dept
9 where deptno = par_deptno;
10 dbms_output.put_line('Average salary for department ' || l_dname ||
11 ' = ' || par_avgsal);
12 end p_deptno;
13
14 function f_avgsal (par_deptno in dept.deptno%type)
15 return number
16 is
17 l_avgsal number;
18 begin
19 select round(avg(e.sal)) into l_avgsal
20 from emp e
21 where e.deptno = par_deptno;
22
23 p_deptno (par_deptno, l_avgsal);
24
25 return l_avgsal;
26 end f_avgsal;
27 select f_avgsal (a.deptno) avg_sal
28 from dept a;
29 /
Result:
AVG_SAL
----------
2917
2175
1567
Average salary for department ACCOUNTING = 2917
Average salary for department RESEARCH = 2175
Average salary for department SALES = 1567
Average salary for department OPERATIONS =
SQL>
Now, adjust it to your tables & data.

Join table in PL/SQL

Hi i would like to ask i get an error when i run this code , can i know how to fix it, i want to know the doctor for this patient when i enter the patient id, thank you. below is my code
DECLARE
patientid(3) := &pt_id;
dname doc_name%type;
BEGIN
SELECT doc_name
INTO dname
FROM doctor
JOIN patient
ON doctor.doc_id = patient.doc_id
WHERE pt_id = patientid;
DBMS_OUTPUT.PUT_LINE('He/She is the patient of Dr.' || dname);
END;
What is patient(3) supposed to be? Datatype is missing!
Though, consider something like this:
SQL> set serveroutput on
SQL> declare
2 -- No : patientid(3):=&pt_id;
3 -- Better : patientid varchar2(3) := &pt_id; -- is it VARCHAR2? or NUMBER? Who knows ...
4 -- Even better:
5 l_pt_id patient.pt_id%type := &par_pt_id;
6 l_dname doctor.doc_name%type;
7 begin
8 select d.doc_name
9 into l_dname
10 from doctor d join patient p on d.doc_id = p.doc_id
11 where p.pt_id = l_pt_id;
12
13 dbms_output.put_line ('He/She is the patient of Dr. '|| l_dname);
14 end;
15 /
Enter value for par_pt_id: 100
He/She is the patient of Dr. Luffy
PL/SQL procedure successfully completed.
SQL>

pl-sql errror on select HR schema

i want to do something like that.. but in a function.. select * from employees where employee_id = &employee_id in HR schema of oracle
Q: Create Function “SEARCH_EMPLOYEE” that receives an Employee ID and returns all its attributes through an output parameter with a data structure that represents all its information
R:
Create or replace FUNCTION get_complete_employee (&in_employee_id IN NUMBER)
AS person_details;
BEGIN
SELECT * --'Name-'||first_name||' '|| last_name
into person_details
FROM employees;
Dbms_output.put_line(person_details);
-- END get_complete_employee;
end;
i have a error of sintax i guess..
i don't know what is wrong
If you are learning how to create the function which returns entire row of the table then following is the basic example of doing the same:
Table data:
SQL> select * from EMP;
EMP_ID EMP_NAME E
---------- -------------------- -
10 John N
20 May Y
SQL>
Function:
SQL> CREATE OR REPLACE FUNCTION GET_COMPLETE_EMPLOYEE (
2 IN_EMPLOYEE_ID IN NUMBER
3 ) RETURN EMP%ROWTYPE AS
4 MY_ROWTYPE EMP%ROWTYPE;
5 BEGIN
6 SELECT
7 * --'Name-'||first_name||' '|| last_name
8 INTO MY_ROWTYPE
9 FROM
10 EMP
11 WHERE
12 EMP_ID = IN_EMPLOYEE_ID;
13
14 RETURN MY_ROWTYPE;
15 END;
16 /
Function created.
SQL>
Calling the function and result:
SQL> SET SERVEROUT ON
SQL>
SQL> DECLARE
2 EMPTYPE EMP%ROWTYPE;
3 BEGIN
4 EMPTYPE := GET_COMPLETE_EMPLOYEE(10);
5 DBMS_OUTPUT.PUT_LINE(EMPTYPE.EMP_NAME);
6 END;
7 /
John
You must have to handle multiple exception scenarios in real-world coding.
Cheers!!

Trying to Understand PLSQL Function

I am new to PLSQL and I have this huge plsql function which am trying to understand and am having hard time understanding the flow and so I would really appreciate if anyone can run me through the big pieces so that I can understand the flow. Guidance would be highly appreciated.
FUNCTION analysis(
REGION_ID_P VARCHAR2,
COUNTRY_ID_P VARCHAR2 ,
SUB_REGION_ID_P VARCHAR2 ,
CUSTOMER_TYPE_ID_P VARCHAR2 ,
RECEIVED_FROM_DATE_P VARCHAR2 ,
RECEIVED_TO_DATE_P VARCHAR2,
CUSTOMER_ID_P VARCHAR2 ,
PRIORITY_ID_P VARCHAR2,
WORK_GROUP_ID_P VARCHAR2,
CITY_ID_P VARCHAR2,
USER_ID_P VARCHAR2
) RETURN ANALYSIS_REPORT_TAB_TYPE pipelined
IS
with_sql LONG;
e_sql LONG;
where_sql LONG;
group_by_sql LONG;
curent_date Date;
v_row ANALYSIS_REPORT_ROW_TYPE := ANALYSIS_REPORT_ROW_TYPE(
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
);
TYPE rectyp IS REF CURSOR; -- define weak REF CURSOR type
rrc_rectyp rectyp;
TYPE recordvar IS RECORD(
MONTHS VARCHAR2(100),
ORDERBY_MONTHS VARCHAR2(100),
REQ_RECEIVED NUMBER(9,2),
REQ_STILL_OPEN NUMBER(9,2),
REQ_AWAIT_ACCEPTANCE NUMBER(9,2),
REQ_WITH_ATT NUMBER(9,2),
REQ_CLOSED NUMBER(9,2),
REQ_CANCELLED NUMBER(9,2)
);
res_rec recordvar;
BEGIN
select sysdate +substr(to_char(systimestamp, 'tzr'),3,1)/24 into curent_date from dual;
where_sql := ' AND 1=1 ';
IF COUNTRY_ID_P IS NOT NULL THEN
where_sql := where_sql ||' AND x.country_id ='|| COUNTRY_ID_P;
END IF;
IF SUB_REGION_ID_P IS NOT NULL THEN
where_sql := where_sql ||' AND x.SUB_REGION_ID ='|| SUB_REGION_ID_P;
END IF;
IF CUSTOMER_TYPE_ID_P IS NOT NULL THEN
where_sql := where_sql ||' AND x.CUSTOMER_TYPE_ID ='|| CUSTOMER_TYPE_ID_P;
END IF;
IF RECEIVED_FROM_DATE_P IS NOT NULL THEN
where_sql := where_sql||' AND convert_time(received_date, ''GMT'', ''GMT'') >= convert_time(trunc(to_date('''||RECEIVED_FROM_DATE_P||''',''dd/mm/yyyy HH24:MI:SS'')), ''Europe/Paris'', ''GMT'')';
END IF;
IF RECEIVED_TO_DATE_P IS NOT NULL THEN
where_sql := where_sql||' AND convert_time(received_date, ''GMT'', ''GMT'') <= convert_time(trunc(to_date('''||RECEIVED_TO_DATE_P||''',''dd/mm/yyyy HH24:MI:SS'')), ''Europe/Paris'', ''GMT'')';
END IF;
IF CUSTOMER_ID_P IS NOT NULL THEN
where_sql := where_sql||' AND x.CUSTOMER_ID in(select CUSTOMER_ID from lk_customer where upper(CUSTOMER_NAME) like upper('''||CUSTOMER_ID_P||'%''))';
END IF;
IF PRIORITY_ID_P IS NOT NULL THEN
where_sql := where_sql ||' AND x.PRIORITY_ID ='|| PRIORITY_ID_P;
END IF;
IF WORK_GROUP_ID_P IS NOT NULL THEN
where_sql := where_sql ||' AND x.WORKGROUP_ID ='|| WORK_GROUP_ID_P;
END IF;
IF CITY_ID_P IS NOT NULL THEN
where_sql := where_sql ||' AND x.CITY_ID = ' || CITY_ID_P;
END IF;
group_by_sql := ' group by to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''mm/YYYY''),to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''yyyy/mm'')';
with_sql := 'with
b AS (select cep_work_item_no from ap_main where req_accept_date is null and ecep_ap_utils.f_business_days(received_date,'''||curent_date||''')>30),
e AS (select cep_work_item_no from ap_main where status_id=1 and req_accept_date is not null and stage_ID != 10 and stage_Id !=4 and ecep_ap_utils.f_business_days(received_date,'''||curent_date||''')>30),
--f AS (select cep_work_item_no from ap_main where received_date is not null),
m AS (select cep_work_item_no from ap_main where received_date is not null and status_id=1),
n AS (select cep_work_item_no from ap_main where status_id=2),
o AS (select cep_work_item_no from ap_main where status_id=3)';
--e_sql := ' SELECT MONTHS, REQ_RECEIVED,REQ_STILL_OPEN, REQ_AWAIT_ACCEPTANCE, REQ_WITH_ATT from (';
--e_sql := with_sql;
e_sql := with_sql||' select to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''mm/YYYY'') MONTHS, to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''yyyy/mm'') ORDERBY_MONTHS,
count(x.cep_work_item_no) REQ_RECEIVED,
count(m.cep_work_item_no) REQ_STILL_OPEN,count(b.cep_work_item_no) REQ_AWAIT_ACCEPTANCE,count(e.cep_work_item_no) REQ_WITH_ATT,
count(n.cep_work_item_no) REQ_CLOSED, count(o.cep_work_item_no) REQ_CANCELLED
from ap_main x,m,b,e,n,o where x.cep_work_item_no=m.cep_work_item_no(+)
and x.cep_work_item_no = b.cep_work_item_no(+) and x.cep_work_item_no=e.cep_work_item_no(+) and
x.cep_work_item_no=n.cep_work_item_no(+) and x.cep_work_item_no=o.cep_work_item_no(+)
and x.received_date is not null';
e_sql := e_sql|| where_sql||group_by_sql;
OPEN rrc_rectyp FOR e_sql;
LOOP
FETCH rrc_rectyp INTO res_rec;
EXIT WHEN rrc_rectyp%NOTFOUND;
v_row.MONTHS := res_rec.MONTHS ;
v_row.ORDERBY_MONTHS := res_rec.ORDERBY_MONTHS ;
v_row.REQ_RECEIVED := res_rec.REQ_RECEIVED;
v_row.REQ_STILL_OPEN := res_rec.REQ_STILL_OPEN;
v_row.REQ_AWAIT_ACCEPTANCE := res_rec.REQ_AWAIT_ACCEPTANCE;
v_row.REQ_WITH_ATT := res_rec.REQ_WITH_ATT;
v_row.REQ_CLOSED := res_rec.REQ_CLOSED;
v_row.REQ_CANCELLED := res_rec.REQ_CANCELLED;
pipe ROW(v_row);
END LOOP;
RETURN;
END analysis;
And would also appreciate if someone can let me know as to what are the important plsql concepts used here so that I can go ahead and understand them in a better way and some small explanation would go long way.
Question:
Is above approach generic way of writing reporting function in your experience or there are some best practices in doing so ?
It looks like a reporting function. It builds an SQL statement with some conditions in it (some elements in the WHERE depend on parameters).
The query itself looks quite complex. It uses the with construct that lets you define sort of an inline view inside the query. That in itself is more an SQL (maybe Oracle SQL) feature and not PLSQL.
Then, the query (which is built up in a string variable) is opened in a cursor. A cursor can be seen as a tool to traverse the result of a query which is done here in a loop.
Then, variables from the cursor are put in properties of v_row. v_row is declared as a record type. It is an object that can respresent a record. The object is piped to the output, meaning that this functions actually returns a recordset, meaning you can call it in a query, like this:
select * from table(monthly_analysis(<parameters>))
[edit]
Addition on request: An example of how you can execute the query within plsql, fetch the results and return them, without building the query as a string. Function is typed from the heart, based on the original. I cannot test it ofcourse, because I don't have the correct database. Actually I don't have a database or editor at all at the moment, so please read between the typo's. ;)
create function Analysis2(
REGION_ID_P VARCHAR2,
COUNTRY_ID_P VARCHAR2,
SUB_REGION_ID_P VARCHAR2,
CUSTOMER_TYPE_ID_P VARCHAR2,
RECEIVED_FROM_DATE_P VARCHAR2,
RECEIVED_TO_DATE_P VARCHAR2,
CUSTOMER_ID_P VARCHAR2,
PRIORITY_ID_P VARCHAR2,
WORK_GROUP_ID_P VARCHAR2,
CITY_ID_P VARCHAR2,
USER_ID_P VARCHAR2)
return
ANALYSIS_REPORT_TAB_TYPE
is
V_RESULTSET ANALYSIS_REPORT_TAB_TYPE;
begin
-- I hope the 'with' construct is supported within PLSQL. I don't have it here on my home laptop so I can't test it.
with
b AS (select cep_work_item_no from ap_main where req_accept_date is null and ecep_ap_utils.f_business_days(received_date,''''||curent_date||'''')>30),
e AS (select cep_work_item_no from ap_main where status_id=1 and req_accept_date is not null and stage_ID != 10 and stage_Id !=4 and
ecep_ap_utils.f_business_days(received_date,''''||curent_date||'''')>30),
--f AS (select cep_work_item_no from ap_main where received_date is not null),
m AS (select cep_work_item_no from ap_main where received_date is not null and status_id=1),
n AS (select cep_work_item_no from ap_main where status_id=2),
o AS (select cep_work_item_no from ap_main where status_id=3)
select
-- You can actually use the record type constructor here to return
-- a specific record type instead of a bunch of loose fields
ANALYSIS_REPORT_REC_TYPE(
to_char(convert_time(received_date, 'GMT', 'Europe/Paris'),'mm/YYYY') MONTHS,
to_char(convert_time(received_date, 'GMT', 'Europe/Paris'),'yyyy/mm') ORDERBY_MONTHS,
count(x.cep_work_item_no) REQ_RECEIVED,
count(m.cep_work_item_no) REQ_STILL_OPEN,
count(b.cep_work_item_no) REQ_AWAIT_ACCEPTANCE,
count(e.cep_work_item_no) REQ_WITH_ATT,
count(n.cep_work_item_no) REQ_CLOSED,
count(o.cep_work_item_no) REQ_CANCELLED)
bulk collect into
V_RESULTSET
from
ap_main x,m,b,e,n,o
where
x.cep_work_item_no=m.cep_work_item_no(+)
and x.cep_work_item_no = b.cep_work_item_no(+) and x.cep_work_item_no=e.cep_work_item_no(+) and
x.cep_work_item_no=n.cep_work_item_no(+) and x.cep_work_item_no=o.cep_work_item_no(+)
and x.received_date is not null
/* Additional where, based on input goes below. I did two, but you get the point */
AND (COUNTRY_ID_P is null or x.country_id = COUNTRY_ID_P)
AND (SUB_REGION_ID_P is null or x.SUB_REGION_ID = SUB_REGION_ID_P)
-- and etc
group by
to_char(convert_time(received_date, 'GMT', 'Europe/Paris'),'mm/YYYY'),
to_char(convert_time(received_date, 'GMT', 'Europe/Paris'),'yyyy/mm');
-- The entire resultset of the query is now stored in V_RESULTSET
-- It can actually be looped using a loop like this:
-- for i in V_RESULTSET.first..V_RESULTSET.last loop
-- DBMS_OUTPUT.PUT_LINE(V_RESULTSET(i).Whateverfield);
-- end loop;
-- But its not needed. The actual query is all this function does, so return its result
return V_RESULTSET;
end;
Are you sure you posted everything? Because as it is now, it will never run successfully. Lot of variables are declared but never used. e_sql for example is being executed but is never assigned a value.
I hope for you that you will not try to learn PL/SQL by looking at this code, because just about every line of code makes me cringe. Especially declaring variables as LONG (which you should never use anymore), the use of that record, and that clumsy date handling. Ouch, ouch, ouch! And above all, if someone writes code like that, that someone should definitely need to learn to comment what he's doing.
Update
I rewrote the function, now that it's complete. I tested it with these auxiliary objects:
SQL> create table ap_main
2 ( cep_work_item_no number
3 , received_date date
4 , req_accept_date date
5 , status_id number
6 , stage_id number
7 , country_id number
8 , sub_region_id number
9 , customer_type_id number
10 , customer_id number
11 , priority_id number
12 , workgroup_id number
13 , city_id number
14 )
15 /
Table created.
SQL> insert into ap_main
2 select 1,sysdate,sysdate,1,4,1,1,1,1,1,1,1 from dual union all
3 select 2,sysdate,sysdate,1,4,1,1,1,1,1,1,1 from dual union all
4 select 3,sysdate,sysdate,1,5,1,1,1,1,1,1,1 from dual union all
5 select 4,sysdate,sysdate,1,5,1,1,1,1,1,1,1 from dual union all
6 select 5,sysdate,sysdate,2,5,1,1,1,1,1,1,1 from dual union all
7 select 6,sysdate-31,sysdate-31,1,5,1,1,1,1,1,1,1 from dual union all
8 select 7,sysdate-31,sysdate-31,1,5,1,1,1,1,1,1,1 from dual union all
9 select 8,sysdate-31,sysdate-31,3,5,1,1,1,1,1,1,1 from dual
10 /
8 rows created.
SQL> create table lk_customer (customer_id,customer_name)
2 as
3 select 1, 'Anna' from dual union all
4 select 2, 'Bob' from dual
5 /
Table created.
SQL> create type analysis_report_row_type as object
2 ( months varchar2(7)
3 , orderby_months varchar2(7)
4 , req_received number
5 , req_still_open number
6 , req_await_acceptance number
7 , req_with_att number
8 , req_closed number
9 , req_cancelled number
10 )
11 /
Type created.
SQL> create type analysis_report_tab_type as table of analysis_report_row_type
2 /
Type created.
SQL> create function convert_time
2 ( p1 in date
3 , p2 in varchar2
4 , p3 in varchar2
5 ) return date
6 is
7 begin
8 return p1;
9 end;
10 /
Function created.
SQL> create package ecep_ap_utils
2 as
3 function f_business_days(p1 in date,p2 in date) return number;
4 end ecep_ap_utils;
5 /
Package created.
SQL> create package body ecep_ap_utils
2 as
3 function f_business_days(p1 in date,p2 in date) return number
4 is
5 begin
6 return p2 - p1;
7 end f_business_days;
8 end ecep_ap_utils;
9 /
Package body created.
Two parameters of your function are not used, so I removed those. All parameters have the wrong type it seems, so I fixed that as well. Furthermore, I removed all the unnecessary variables and made your query use bind variables. This is important, because Oracle stores each unique parsed statement in the shared pool for reuse. But by glueing in your parameters, you made every statement unique, causing a hard parse and filling up your shared pool.
Your function is a pipelined function, which seems like overkill in your situation, since your resultset won't be very large, because you are grouping by the month. So you'll only get one row per month. I left that in place. The query accessed your ap_main table six times, where one time is sufficient. You'll probably notice that by a performance gain. What still worries me is the date handling. The original coder couldn't makes his mind up whether he'd like to use strings or dates to handle dates. Of course you should be using dates for handling dates. A lot of the conversion routines that are called can probably be skipped somehow. Anyways ... here is the new function:
SQL> create function analysis
2 ( country_id_p in number
3 , sub_region_id_p in number
4 , customer_type_id_p in number
5 , received_from_date_p in date
6 , received_to_date_p in date
7 , customer_id_p in number
8 , priority_id_p in number
9 , work_group_id_p in number
10 , city_id_p in number
11 ) return analysis_report_tab_type pipelined
12 is
13 l_current_date date;
14 l_refcursor sys_refcursor;
15 l_analysis_report_row analysis_report_row_type := analysis_report_row_type(null,null,null,null,null,null,null,null);
16 begin
17 select sysdate + to_number(to_char(systimestamp, 'tzh')) / 24
18 into l_current_date
19 from dual
20 ;
21 open l_refcursor for
22 'select analysis_report_row_type
23 ( to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''mm/yyyy'')
24 , to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''yyyy/mm'')
25 , count(cep_work_item_no)
26 , count(case when received_date is not null and status_id=1 then 1 end)
27 , count(case when req_accept_date is null and ecep_ap_utils.f_business_days(received_date,:p_current_date)>30 then 1 end)
28 , count(case when req_accept_date is not null and status_id = 1 and stage_ID not in (4,10) and ecep_ap_utils.f_business_days(received_date,:p_current_date)>30 then 1 end)
29 , count(case when status_id = 2 then 1 end)
30 , count(case when status_id = 3 then 1 end)
31 )
32 from ap_main
33 where received_date is not null ' ||
34 case
35 when country_id_p is null then
36 ' and (1=1 or :p_country_id is null)'
37 else
38 ' and country_id = :p_country_id'
39 end ||
40 case
41 when sub_region_id_p is null then
42 ' and (1=1 or :p_sub_region_id is null)'
43 else
44 ' and sub_region_id = :p_sub_region_id'
45 end ||
46 case
47 when customer_type_id_p is null then
48 ' and (1=1 or :p_customer_type_id is null)'
49 else
50 ' and customer_type_id = :p_customer_type_id'
51 end ||
52 case
53 when received_from_date_p is null then
54 ' and (1=1 or :p_received_from_date is null)'
55 else
56 ' and convert_time(received_date, ''GMT'', ''GMT'') >= convert_time(trunc(:p_received_from_date), ''Europe/Paris'', ''GMT'')'
57 end ||
58 case
59 when received_to_date_p is null then
60 ' and (1=1 or :p_received_to_date is null)'
61 else
62 ' and convert_time(received_date, ''GMT'', ''GMT'') <= convert_time(trunc(:p_received_to_date), ''Europe/Paris'', ''GMT'')'
63 end ||
64 case
65 when customer_id_p is null then
66 ' and (1=1 or :p_customer_id is null)'
67 else
68 ' and customer_id in (select customer_id from lk_customer where upper(customer_name) like upper(:p_customer_id || ''%''))'
69 end ||
70 case
71 when priority_id_p is null then
72 ' and (1=1 or :p_priority_id is null)'
73 else
74 ' and priority_id = :p_priority_id'
75 end ||
76 case
77 when work_group_id_p is null then
78 ' and (1=1 or :p_workgroup_id is null)'
79 else
80 ' and workgroup_id = :p_workgroup_id'
81 end ||
82 case
83 when city_id_p is null then
84 ' and (1=1 or :p_city_id is null)'
85 else
86 ' and city_id = :p_city_id'
87 end ||
88 ' group by to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''mm/yyyy'')
89 , to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''yyyy/mm'')'
90 using l_current_date
91 , l_current_date
92 , country_id_p
93 , sub_region_id_p
94 , customer_type_id_p
95 , received_from_date_p
96 , received_to_date_p
97 , customer_id_p
98 , priority_id_p
99 , work_group_id_p
100 , city_id_p
101 ;
102 loop
103 fetch l_refcursor into l_analysis_report_row;
104 exit when l_refcursor%notfound;
105 pipe row (l_analysis_report_row);
106 end loop;
107 return;
108 end analysis;
109 /
Function created.
And to prove that the new functions works:
SQL> select * from table(analysis(1,1,1,null,null,1,1,1,1))
2 /
no rows selected
SQL> select * from table(analysis(null,null,null,null,null,null,null,null,null))
2 /
MONTHS ORDERBY REQ_RECEIVED REQ_STILL_OPEN REQ_AWAIT_ACCEPTANCE REQ_WITH_ATT REQ_CLOSED REQ_CANCELLED
------- ------- ------------ -------------- -------------------- ------------ ---------- -------------
12/2010 2010/12 5 4 0 0 1 0
11/2010 2010/11 3 2 0 2 0 1
2 rows selected.
Update 2
Here are two links to the two crucial constructs used here:
OPEN FOR statement: http://download.oracle.com/docs/cd/E11882_01/appdev.112/e17126/openfor_statement.htm#sthref1703
Pipelined functions: http://download.oracle.com/docs/cd/E11882_01/appdev.112/e17126/tuning.htm#sthref1129
As you can see in the OPEN FOR statement documentation, after FOR you specify a query, which I construct dynamically. The same was being done in your original code. Differences are that I'm using native dynamic SQL, so I can use bind variables (the variables starting with ":p_"). I did it in such a way that no matter what input values I provide, all the bind variables are present in the query. Here is a good explanation of why and how: http://www.oracle.com/technetwork/issue-archive/2009/09-jul/o49asktom-090487.html
If you have some more questions, don't hesitate to ask.
Regards,
Rob.
A good way to figure stuff like this out is to step through the code in the debugger. Oracle offers a free tool called SQL Developer, which comes with a dubugger, so you could use that.
At initial glance, this code looks like it's building a dynamic SQL statement to fetch some data. By dynamic, I mean that the SQL statement is built at runtime, and the procedure is constructing the where clause based on passed in parameters.
At the end, they're doing:
OPEN rrc_rectyp FOR e_sql
Which basically puts the result from the query in a ref cursor, and this allows the client to get the result data.
By the way, using dynamic SQL in this manner is very bad for performance since it results in a hard parse. You can read more about hard parses and why they are evil at this link. The solution is to use a context, so you end up with the advantages of bind variables and avoid the hard parse (this is discussed at that link).
EDIT
Actually, they are pipelining the result data into a collection variable. See this link, and search for "Assigning the Result of a Table Function".