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".
Related
---These columns in v_array has counts in it but when i run this code ,its returns zero counts for all columns , where is my mistake ,please help me in it ----
set serveroutput on
declare
type t_list is table of varchar(50) index by PLS_INTEGER;
r_emp t_list;
type e_list is varray(4) of varchar(50);
v_array e_list;
begin
v_array:=e_list('CLAIRTY_ID','SERV_ACCT_NUMBER','BB_ACQUISITION_TYPE','ONT_ACQ_TYPE');
for i in 1..4 loop
select sum(case when v_array(i) is null then 1 else 0 end) into r_emp(i) from SZ_GOODS_DETAIL;
dbms_output.put_line( r_emp(i));
end loop;
end;
--------------output--------------------
0
0
0
0
PL/SQL procedure successfully completed.
You seem to be trying to count the number of null values in each of the columns included in your collection. When you do:
select sum(case when v_array(i) is null then 1 else 0 end) into r_emp(i) from SZ_GOODS_DETAIL;
you are counting how many rows in the table the collection element v_array(i) is null for - which is either going to always be zero (if the collection element is not null, as all your are) or the number of rows in the table (if the element is null). Whether that element is null is nothing to do with the data in the table.
If you want to evaluate the column value corresponding to the value of that collection element for each row in the table then you would either need evaluate the element name with something like:
select sum(case
when v_array(i) = 'CLAIRTY_ID' and CLAIRTY_ID is null then 1
when v_array(i) = 'SERV_ACCT_NUMBER' and SERV_ACCT_NUMBER is null then 1
when v_array(i) = 'BB_ACQUISITION_TYPE' and BB_ACQUISITION_TYPE is null then 1
when v_array(i) = 'ONT_ACQ_TYPE' and ONT_ACQ_TYPE is null then 1
else 0
end)
into r_emp(i)
... which makes the collection a bit pointless and probably isn't what you were thinking of, or use dynamic SQL to embed the element value as the column name:
declare
type t_list is table of number index by PLS_INTEGER;
r_emp t_list;
type e_list is varray(4) of varchar2(50);
v_array e_list;
v_stmt varchar2(255);
begin
v_array:=e_list('CLAIRTY_ID','SERV_ACCT_NUMBER','BB_ACQUISITION_TYPE','ONT_ACQ_TYPE');
for i in 1..4 loop
v_stmt := 'select sum(case when "' || v_array(i) || '" is null then 1 else 0 end) from SZ_GOODS_DETAIL';
-- just for debugging
dbms_output.put_line(v_stmt);
execute immediate v_stmt into r_emp(i);
dbms_output.put_line(r_emp(i));
end loop;
end;
/
Each iteration of the loop will construct and execute a dynamic statement like:
select sum(case when "CLAIRTY_ID" is null then 1 else 0 end) from SZ_GOODS_DETAIL
... which you can see in the debug output (which you can obviously remove once it's working).
You could also do the calculation using count(), subtracting the number of non-null values form the number of rows:
v_stmt := 'select count(*) - count("' || v_array(i) || '") from SZ_GOODS_DETAIL';
Or you could avoid PL/SQL and use an XML trick:
select column_value,
to_number(xmlquery('/ROWSET/ROW/C/text()'
passing xmltype(dbms_xmlgen.getxml(
'select count(case when "' || column_value || '" is null then 1 end) as c '
|| 'from SZ_GOODS_DETAIL'))
returning content)) as c
from sys.odcivarchar2list('CLAIRTY_ID','SERV_ACCT_NUMBER','BB_ACQUISITION_TYPE','ONT_ACQ_TYPE')
fiddle
What do you expect as a result? What does SZ_GOODS_DETAIL table have to do with it? I guess you wanted to compare collection contents to values stored in that table, but - your code doesn't do that.
Here's what I think you want (which doesn't have to be true):
Table contents:
SQL> SELECT * FROM sz_goods_detail;
ID NAME
---------- -------------------
1 CLAIRTY_ID
2 CLAIRTY_ID
3 BB_ACQUISITION_TYPE
SQL> SET SERVEROUTPUT ON
Modified procedure (see line #19):
SQL> DECLARE
2 TYPE t_list IS TABLE OF VARCHAR (50)
3 INDEX BY PLS_INTEGER;
4
5 r_emp t_list;
6
7 TYPE e_list IS VARRAY (4) OF VARCHAR (50);
8
9 v_array e_list;
10 BEGIN
11 v_array :=
12 e_list ('CLAIRTY_ID',
13 'SERV_ACCT_NUMBER',
14 'BB_ACQUISITION_TYPE',
15 'ONT_ACQ_TYPE');
16
17 FOR i IN 1 .. 4
18 LOOP
19 SELECT SUM (CASE WHEN v_array (i) = s.name THEN 1 ELSE 0 END)
20 INTO r_emp (i)
21 FROM sz_goods_detail s;
22
23 DBMS_OUTPUT.put_line (v_array(i) ||': '|| r_emp (i));
24 END LOOP;
25 END;
26 /
Result:
CLAIRTY_ID: 2
SERV_ACCT_NUMBER: 0
BB_ACQUISITION_TYPE: 1
ONT_ACQ_TYPE: 0
PL/SQL procedure successfully completed.
SQL>
If you want to use dynamic column names then you need to use dynamic SQL:
DECLARE
r_emp SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST();
v_array SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'CLAIRTY_ID','SERV_ACCT_NUMBER','BB_ACQUISITION_TYPE','ONT_ACQ_TYPE'
);
BEGIN
DBMS_OUTPUT.ENABLE;
FOR i IN 1..v_array.COUNT LOOP
r_emp.EXTEND;
EXECUTE IMMEDIATE
'SELECT SUM(CASE WHEN "' || v_array(i) || '" IS NULL THEN 1 ELSE 0 END) FROM SZ_GOODS_DETAIL'
INTO r_emp(i);
dbms_output.put_line(v_array(i) || ': ' || r_emp(i));
END LOOP;
END;
/
Which, for the sample data:
CREATE TABLE SZ_GOODS_DETAIL (CLAIRTY_ID, SERV_ACCT_NUMBER, BB_ACQUISITION_TYPE, ONT_ACQ_TYPE) AS
SELECT 1, 1, 1, 1 FROM DUAL UNION ALL
SELECT 1, 1, 1, NULL FROM DUAL UNION ALL
SELECT 1, 1, NULL, NULL FROM DUAL UNION ALL
SELECT 1, NULL, NULL, NULL FROM DUAL;
Outputs:
CLAIRTY_ID: 0
SERV_ACCT_NUMBER: 1
BB_ACQUISITION_TYPE: 2
ONT_ACQ_TYPE: 3
fiddle
The input parms are 2 ids(from_id and to_id) that are comma separated. Each id must be evaluated as the report is run . From_id='202031,202032,202035,202041,...'
To_id ='202111,202112,202135,202141,...'
Should filter the records for each from _id and while printing to log file the corresponding to_id should be printed. Always be 202031 to 202111, 202032 to 202112, 202035 to 202135, 20241 to 202141 . Below is the expected result.
Num_ber Description person_id person_name Rolled_To_term From_id to_id term_priority_to_term max_amt_to_term
90010001 fund1 1 abc N 202031 202111 3 23
90010001 fund1 1 abc N 202032 202112 6 110
50010001 fund2 2 xyz N 202035 202135 2 45
50010001 fund2 3 efg N 202035 202135 5 50
Below procedure updates the to_id ,but the from_id remains 202031 and gets looped again . The cursor does not filter for the other frm_id's.
PROCEDURE report_v1 (
p_frm_id_list VARCHAR2,
p_to_id_list VARCHAR2
) AS
exemp_log_file utl_file.file_type;
log_msg VARCHAR2(600);
frm_id_list VARCHAR2(50);
frm_id_list_temp VARCHAR2(50);
frm_id VARCHAR2(50);
to_id_list VARCHAR2(50);
to_id_list _temp VARCHAR2(50);
to_id VARCHAR2(50);
CURSOR get_expt_info_c IS
SELECT
code AS num_ber,
desc AS description,
a.term_expiration AS exp_term,
(select p_id from tab3 c where a.id=c.id) as person_id,
(select p_name from tab3 c where a.id=c.id) as person_name,
a.term_code AS from_id,
nvl(
(
SELECT DISTINCT
'Y'
FROM
tab2 b
WHERE
b.term_code = to_term
AND b.id = a.id
), 'N'
) rolled_to_term,
a.priority AS term_priority_to_term,
a.amount AS max_amt_to_term
FROM
tab left
JOIN tab2 a ON code = a.code
AND term_code = a.term_code
WHERE
instr(
frm_id, a.term_code
) > 0;
BEGIN
exemp_log_file := utl_file.fopen(dir,'report.csv','w');
log_msg := 'Report header';
utl_file.put_line(exemp_log_file,log_msg,autoflush => true );
frm_id_list := replace(p_frm_id_list ,' ','');
to_id_list := replace( p_ to_id_list, ' ','' );
WHILE frm_id_list IS NOT NULL LOOP
IF instr( frm_id_list,',') > 0 THEN
frm_id := substr(frm_id_list,1,instr(frm_id_list,',') - 1 );
frm_id_list_temp := substr(frm_id_list,instr(frm_id_list,',' ) + 1);
ELSE
frm_id := frm_id_list;
frm_id_list_temp := NULL;
END IF;
frm_id_list := frm_id_list_temp;
WHILE to_id_list IS NOT NULL LOOP
IF instr(to_id_list,',' ) > 0 THEN
to_id := substr(to_id_list,1,instr(to_id_list,',' ) - 1 );
to_id_list_temp := substr(to_id_list,instr(to_id_list,',' ) + 1 );
ELSE
to_id := to_id_list;
to_id_list_temp := NULL;
END IF;
to_id_list := to_id_list_temp;
FOR exemp IN get_expt_info_c LOOP
utl_file.put_line(
exemp_log_file,
exemp.num_ber
|| ';'
|| exemp.description
|| ';'
|| exemp.exp_term
|| ';'
|| exemp.person_id
|| ';'
|| exemp.person_name
|| ';'
|| exemp.from_id
|| ';'
|| exemp.rolled_to_term
|| ';'
|| exemp.priority
|| ';'
|| exemp.amt
|| ';'
|| to_id,
autoflush => true
);
END LOOP;
END LOOP;
END LOOP;
EXCEPTION
WHEN no_data_found THEN
NULL;
WHEN OTHERS THEN
EXIT;
utl_file.fclose(exemp_log_file);
END report_v1;
TABLE DEFINITION &Sample DATA
CREATE TABLE "TAB"
(CODE" VARCHAR2(8 CHAR) NOT NULL ENABLE,
"TERM_CODE" VARCHAR2(6 CHAR) NOT NULL ENABLE,
"DESC" VARCHAR2(30 CHAR) NOT NULL ENABLE,
"DETAIL_CODE" VARCHAR2(4 CHAR) NOT NULL ENABLE,
"ACTIVITY_DATE" DATE NOT NULL ENABLE
)
CODE TERM_CODE DESC DETAIL_CODE ACTIVITY_DATE
90010001 202031 fund1 aaa 09-Jun-2009
CREATE TABLE."TAB2"
("CODE" VARCHAR2(8 CHAR),
"ID" NUMBER(8,0),
"TERM_CODE" VARCHAR2(6 CHAR),
"ACTIVITY_DATE" DATE,
"TERM_EXPIRATION" VARCHAR2(6 CHAR),
"PRIORITY" NUMBER(2,0),
"AMOUNT" NUMBER(7,2)
)
CODE ID TERM_CODE ACTIVITY_DATE TERM_EXPIRATION PRIORITY AMOUNT
90010001 1 202031 09-Jun-2009 31-DEC-2030 3 23
CREATE TABLE "TAB3"
("P_ID" NUMBER(8,0) NOT NULL ENABLE,
"P_NAME" VARCHAR2(60 CHAR) NOT NULL ENABLE,
"ACTIVITY_DATE" DATE NOT NULL ENABLE
)
P_ID P_NAME ACTIVITY_DATE
1 abc 01-JAN-2007
Can anybody suggest a better way /alternative to my approach or correct my procedure.
Your description indicated you are splitting 2 CSV strings into their individual elements them combining the split elements into a set of 2 elements, one from each string. This can be accomplished in a single sql statement:
with parms( from_ids, to_ids) as
( select '202031,202032,202035,202041'
, '202111,202112,202135,202141'
from dual
)
select from_id, to_id
from ( select regexp_substr (from_ids, '[^,]+', 1, rownum) from_id
, regexp_substr (to_ids, '[^,]+', 1, rownum) to_id
, 1+length(from_ids)-length(replace(from_ids,',','')) cf
, 1+ length(to_ids)-length(replace(to_ids,',','')) ct
from parms
connect by level <= length (regexp_replace (from_ids, '[^,]+')) + 1
)
where cf=ct;
If the initial strings do not contain the same number of elements the query returns no rows.
I am not sure what to do with your procedure as what you have posted is invalid and did not produce any results other than compile errors. It does not reference the parameter "from_id_list" but does use the variants "frm_id_list" (defined) and "p_from_id_list" (not defined). As for the parameter "to_id_list" it attempts to use it an an LVALUE in assignment. Since it is defined as an IN parameter this is not allowed. Further the procedure does not refer to any local variables except "log_msg" and "exempt_log_file". Looks like you posted a prior version by mistake. You may want to edit your question and post the correct version.
I does appear however that the 2 WHILE loops a are attempting to do the split and combine process. The query above replaces both. But how does that fit into the cursor loop?
So, I have a project I built a while back that is pretty much identical to this one on github: https://github.com/DevNambi/sql-server-regex
You may want to download that, get it installed (C# deterministic regex functions wont break SQL Server), and then use Matches to get a result set of your matches and their values. With would allow something like this..
SELECT S.RelevantColumns, TRY_CONVERT(INT, Match) as ID
FROM SourceTable S
CROSS APPLY dbo.RegexMatches(S.IDlist, '(\d),') x
INNER JOIN dbo.SomeOtherTable OT
ON OT.ID = TRY_CONVERT(INT, x.Match)
I've done this a few times when a reporting layout was not conducive to iteration like your cursor above. Let me know if this helps. I'm unsure entirely what the purpose of the entire script is, but hopefully this provides some insight into how to achieve this?
I've got a table named "F_ParqueInfra", that I'd like to count all values in it where the value is equal to -1.
So, this table has 11 columns and 833 rows = 9.163 number of data in this table.
I'd like to know, how many "-1" values has in the whole table (all columns), in the simplest way.
Also I'll do that with a lot of tables in my Data Warehouse.
Really thanks!
One option is to use dynamic SQL. For example:
SQL> select * from f_parqueinfra;
ID_USUARIO ID_EMPRESA ID_DEPARTAMENTO
---------- ---------- ---------------
250 32 12
-1 -1 -1
0 -1 1
5 2 -1
SQL> set serveroutput on;
SQL> declare
2 l_table_name varchar2(30) := 'F_PARQUEINFRA';
3 l_value number := -1; -- search value
4 l_str varchar2(200); -- to compose SELECT statement
5 l_cnt number := 0; -- number of values in one column
6 l_sum number := 0; -- total sum of values
7 begin
8 for cur_r in (select table_name, column_name
9 from user_tab_columns
10 where table_name = l_table_name
11 and data_type = 'NUMBER'
12 )
13 loop
14 l_str := 'select count(*) from ' ||cur_r.table_name ||
15 ' where ' || cur_r.column_name || ' = ' || l_value;
16 execute immediate l_str into l_cnt;
17 l_sum := l_sum + l_cnt;
18 end loop;
19 dbms_output.put_line('Number of ' || l_value ||' values = ' || l_sum);
20 end;
21 /
Number of -1 values = 5
PL/SQL procedure successfully completed.
SQL>
If you change l_value (line #3), you can search for some other value, e.g.
SQL> l3
3* l_value number := -1; -- search value
SQL> c/-1/250
3* l_value number := 250; -- search value
SQL> /
Number of 250 values = 1
PL/SQL procedure successfully completed.
SQL>
Or, you can change table name (line #2) and search some other table.
Basically, you'd probably want to turn that anonymous PL/SQL code into a function which would accept table name and search value and return number of appearances. That shouldn't be too difficult so I'll leave it to you, for practice.
[EDIT: converting it into a function]
Although far from being perfect, something like this will let you search for some numeric and string values in tables in current schema. Dates are more complex, depending on formats etc. but - for simple cases - this code might be OK for you. Have a look:
SQL> create or replace function f_cnt (par_table_name in varchar2,
2 par_data_type in varchar2,
3 par_value in varchar2)
4 return sys.odcivarchar2list
5 is
6 l_data_type varchar2(20) := upper(par_data_type);
7 l_retval sys.odcivarchar2list := sys.odcivarchar2list();
8 l_str varchar2(200); -- to compose SELECT statement
9 l_out varchar2(200); -- return value
10 l_cnt number := 0; -- number of values in one column
11 l_sum number := 0; -- total sum of values
12 begin
13 -- loop through all tables in current schema
14 for cur_t in (select table_name
15 from user_tables
16 where table_name = upper(par_table_name)
17 or par_table_name is null
18 )
19 loop
20 -- reset the counter
21 l_sum := 0;
22 -- loop through all columns in that table
23 for cur_c in (select column_name
24 from user_tab_columns
25 where table_name = cur_t.table_name
26 and data_type = l_data_type
27 )
28 loop
29 -- pay attention to search value's data type
30 if l_data_type = 'VARCHAR2' then
31 l_str := 'select count(*) from ' ||cur_t.table_name ||
32 ' where ' || cur_c.column_name || ' = ' ||
33 chr(39) || par_value ||chr(39);
34 elsif l_data_type = 'NUMBER' then
35 l_str := 'select count(*) from ' ||cur_t.table_name ||
36 ' where ' || cur_c.column_name || ' = ' || par_value;
37 end if;
38
39 execute immediate l_str into l_cnt;
40 l_sum := l_sum + l_cnt;
41 end loop;
42
43 if l_sum > 0 then
44 l_out := cur_t.table_name ||' has ' || l_sum ||' search values';
45 l_retval.extend;
46 l_retval(l_retval.count) := l_out;
47 end if;
48 end loop;
49 return l_retval;
50 end;
51 /
Function created.
Testing:
SQL> select * From table(f_cnt(null, 'number', -1));
COLUMN_VALUE
-----------------------------------------------------------------
F_PARQUEINFRA has 5 search values
SQL> select * From table(f_cnt(null, 'varchar2', 'KING'));
COLUMN_VALUE
-----------------------------------------------------------------
EMP has 1 search values
SQL>
This might be a good place to use the unpivot syntax. This still requires you to type all the column names once - but not more.
Here is an example for 4 columns:
select count(*) cnt
from mytable
unpivot(myval for mycol in (col1, col2, col3, col4))
where myval = -1
As a bonus, you can easily modify the query to get the number of -1 per column:
select mycol, count(*) cnt
from mytable
unpivot(myval for mycol in (col1, col2, col3, col4))
where myval = -1
group by mycol
This should give you what you need.
Notes:
performs true numeric comparison (for example would match -1.00 also) using an inline function to prevent error should the value in a compared cell be non-numeric. (if all your compared values are guaranteed to be numeric the inline function can be simplified dramatically)
searches only varchar2 and number column types (this can be changed if desired).
The code follows:
set serveroutput on size 10000
declare
vMyTableName varchar2(200) := 'F_ParqueInfra';
vMyValue number := -1;
vSQL varchar2(4000);
vTotal pls_integer;
vGrandTotal number(18) := 0;
cursor c1 is
select *
from user_tab_columns
where table_name = vMyTableName;
vInlineFn varchar2(4000) := 'with
function matchesMyValue(value varchar2) return pls_integer
is
begin
return case
when to_number(value) = '||vMyValue||' then
1
else
0
end;
exception
when value_error then
return 0;
end;
';
begin
for x in c1 loop
if x.data_type in ('VARCHAR2','NUMBER') then -- only looking in these column data types for -1 but you can flex this to suit your column type definitions
vSQL := 'select sum(matchesMyValue('||x.column_name||')) from '||vMyTableName;
execute immediate vInlineFn||vSQL into vTotal;
vGrandTotal := vGrandTotal + vTotal;
end if;
end loop;
dbms_output.put_line('Total cells containing -1 = '||vGrandTotal);
end;
/
I have column in my database where the values are coming like the following:
3862,3654,3828
In dummy column any no. of comma separated values can come. I tried with following query but it is creating duplicate results.
select regexp_substr(dummy,'[^,]+',1,Level) as dummycol
from (select * from dummy_table)
connect by level <= length(REGEXP_REPLACE(dummy,'[^,]+'))+1
I am not understanding the problem. Can anyone can help?
Works perfectly for me -
SQL> WITH dummy_table AS(
2 SELECT '3862,3654,3828' dummy FROM dual
3 )
4 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
5 FROM dummy_table
6 CONNECT BY level <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
7 /
DUMMYCOL
--------------
3862
3654
3828
SQL>
There are many other ways of achieving it. Read Split single comma delimited string into rows.
Update Regarding the duplicates when the column is used instead of a single string value. Saw the use of DBMS_RANDOM in the PRIOR clause to get rid of the cyclic loop here
Try the following,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
7 FROM dummy_table
8 CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
9 AND prior rn = rn
10 AND PRIOR DBMS_RANDOM.VALUE IS NOT NULL
11 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
SQL>
Update 2
Another way,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678,xyz' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(t.dummy, '[^,]+', 1, levels.column_value)) AS dummycol
7 FROM dummy_table t,
8 TABLE(CAST(MULTISET
9 (SELECT LEVEL
10 FROM dual
11 CONNECT BY LEVEL <= LENGTH (regexp_replace(t.dummy, '[^,]+')) + 1
12 ) AS sys.OdciNumberList)) LEVELS
13 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
xyz
6 rows selected.
SQL>
Giving a PL/SQL example where parsing over a table with an ID and column name. This will parse and print out each ID and the parsed value which could then be inserted into a new table or used in some other way.
Input
Column_ID Column_Name
123 (3862,3654,3828)
Output
Column_ID Column_Name
123 3862
123 3654
123 3828
PL/SQL Code
declare
table_name1 varchar2(1000);
string_to_parse varchar2(2000); -- assign string to table name
string_length number := 0; -- string length for loop
string_value varchar2(2000); -- string value to store value in
column_id number;
begin
--some table in the format '123' as column_id, '(3862,3654,3828)' as column_name
--remove the parenthesis or other special characters if needed
update some_table t
set t.column_name = regexp_replace(t.column_name,'\(|\)','');
commit;
for i in (
select * from some_table
) loop
column_id := i.column_id; --assign the id of the colors
string_to_parse := i.column_name; -- assign string to be parsed
if string_to_parse is null then
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
else
--String to parse is the comma
string_to_parse := string_to_parse||',';
string_length := length(string_to_parse) - length(replace(string_to_parse,',',''));
-- Loop through string from parameter
for i in 1 .. string_length loop
-- [^,] matches any character except for the ,
select regexp_substr(string_to_parse,'[^,]+',1,i)
into string_value -- stores value into string_value
from dual; -- dual is a dummy table to work around
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
--clear out the string value
string_value := null;
end loop;
end if;
end loop;
end;
I have column in my database where the values are coming like the following:
3862,3654,3828
In dummy column any no. of comma separated values can come. I tried with following query but it is creating duplicate results.
select regexp_substr(dummy,'[^,]+',1,Level) as dummycol
from (select * from dummy_table)
connect by level <= length(REGEXP_REPLACE(dummy,'[^,]+'))+1
I am not understanding the problem. Can anyone can help?
Works perfectly for me -
SQL> WITH dummy_table AS(
2 SELECT '3862,3654,3828' dummy FROM dual
3 )
4 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
5 FROM dummy_table
6 CONNECT BY level <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
7 /
DUMMYCOL
--------------
3862
3654
3828
SQL>
There are many other ways of achieving it. Read Split single comma delimited string into rows.
Update Regarding the duplicates when the column is used instead of a single string value. Saw the use of DBMS_RANDOM in the PRIOR clause to get rid of the cyclic loop here
Try the following,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
7 FROM dummy_table
8 CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
9 AND prior rn = rn
10 AND PRIOR DBMS_RANDOM.VALUE IS NOT NULL
11 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
SQL>
Update 2
Another way,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678,xyz' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(t.dummy, '[^,]+', 1, levels.column_value)) AS dummycol
7 FROM dummy_table t,
8 TABLE(CAST(MULTISET
9 (SELECT LEVEL
10 FROM dual
11 CONNECT BY LEVEL <= LENGTH (regexp_replace(t.dummy, '[^,]+')) + 1
12 ) AS sys.OdciNumberList)) LEVELS
13 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
xyz
6 rows selected.
SQL>
Giving a PL/SQL example where parsing over a table with an ID and column name. This will parse and print out each ID and the parsed value which could then be inserted into a new table or used in some other way.
Input
Column_ID Column_Name
123 (3862,3654,3828)
Output
Column_ID Column_Name
123 3862
123 3654
123 3828
PL/SQL Code
declare
table_name1 varchar2(1000);
string_to_parse varchar2(2000); -- assign string to table name
string_length number := 0; -- string length for loop
string_value varchar2(2000); -- string value to store value in
column_id number;
begin
--some table in the format '123' as column_id, '(3862,3654,3828)' as column_name
--remove the parenthesis or other special characters if needed
update some_table t
set t.column_name = regexp_replace(t.column_name,'\(|\)','');
commit;
for i in (
select * from some_table
) loop
column_id := i.column_id; --assign the id of the colors
string_to_parse := i.column_name; -- assign string to be parsed
if string_to_parse is null then
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
else
--String to parse is the comma
string_to_parse := string_to_parse||',';
string_length := length(string_to_parse) - length(replace(string_to_parse,',',''));
-- Loop through string from parameter
for i in 1 .. string_length loop
-- [^,] matches any character except for the ,
select regexp_substr(string_to_parse,'[^,]+',1,i)
into string_value -- stores value into string_value
from dual; -- dual is a dummy table to work around
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
--clear out the string value
string_value := null;
end loop;
end if;
end loop;
end;