How to find last iteration in below for loop - oracle

I have following Oracle query. I am trying to find last index in the iteration, In other words, I want to print the result only in last step. But I have no success
set serveroutput on
DECLARE
str VARCHAR2(100) := 'a,c,v,b';
V_CMP_MUMBER VARCHAR2(20);
V_CMP_MUMBERS VARCHAR2(200);
BEGIN
FOR i IN
(SELECT trim(regexp_substr(str, '[^,]+', 1, LEVEL)) l
FROM dual
CONNECT BY LEVEL <= regexp_count(str, ',')+1
)
LOOP
select cn INTO V_CMP_MUMBER from VP where NAME=i.l;
V_CMP_MUMBERS := V_CMP_MUMBERS || ',' || v_cmp_mumber;
dbms_output.put_line(REGEXP_REPLACE(V_CMP_MUMBERS,'^,', '' ));
END LOOP;
END;
/

Don't use a loop and do it all in a single SQL query:
DECLARE
str VARCHAR2(100) := 'a,c,v,b';
V_CMP_MUMBERS VARCHAR2(200);
v_count PLS_INTEGER;
BEGIN
SELECT COUNT(*),
LISTAGG(cn, ',')
WITHIN GROUP (ORDER BY INSTR(','||str||',', ','||name||','))
INTO v_count,
V_CMP_MUMBERS
FROM VP
WHERE INSTR(','||str||',', ','||name||',') > 0;
dbms_output.put_line('Number of rows matched: ' || v_count);
dbms_output.put_line('Matches: ' || V_CMP_MUMBERS);
END;
/
Which, for the sample data:
CREATE TABLE vp (name, cn) AS
SELECT 'a', 'aaa' FROM DUAL UNION ALL
SELECT 'b', 'bbb' FROM DUAL UNION ALL
SELECT 'c', 'ccc' FROM DUAL UNION ALL
SELECT 'v', 'vvv' FROM DUAL;
Outputs:
Number of rows matched: 4
Matches: aaa,ccc,vvv,bbb
db<>fiddle here

Move dbms_output.put_line call out of the loop.
For my sample table:
SQL> select * from vp;
CN N
---------- -
100 a
200 b
300 c
400 v
SQL>
result is then
SQL> DECLARE
2 str VARCHAR2(100) := 'a,c,v,b';
3 V_CMP_MUMBER VARCHAR2(20);
4 V_CMP_MUMBERS VARCHAR2(200);
5 l_last_index number := 0;
6 BEGIN
7 FOR i IN
8 (SELECT trim(regexp_substr(str, '[^,]+', 1, LEVEL)) l
9 FROM dual
10 CONNECT BY LEVEL <= regexp_count(str, ',')+1
11 )
12 LOOP
13 l_last_index := l_last_index + 1;
14 select cn INTO V_CMP_MUMBER from VP where NAME=i.l;
15 V_CMP_MUMBERS := V_CMP_MUMBERS || ',' || v_cmp_mumber;
16 END LOOP;
17 dbms_output.put_line('Last index = ' || l_last_index);
18 dbms_output.put_line(REGEXP_REPLACE(V_CMP_MUMBERS,'^,', '' ));
19 END;
20 /
Last index = 4
100,300,400,200
PL/SQL procedure successfully completed.
SQL>

Related

Is this possible to apply a function to every fields of table (groupy by type)

I would like to replace every string are null by 'n' and every number by 0.
Is there a way to do that?
With a polymorphic table function I can select all the columns of a certain type but I can't modify the value of the columns.
Yes, it is possible with PTF also. You may modify columns in case you've set pass_through to false for the column in the describe method (to drop it) and copy it into new_columns parameter of the describe.
Below is the code:
create package pkg_nvl as
/*Package to implement PTF*/
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
;
procedure fetch_rows;
end pkg_nvl;
/
create package body pkg_nvl as
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
as
modif_cols dbms_tf.columns_new_t;
new_col_cnt pls_integer := 0;
begin
/*Mark input columns as used and as modifiable for subsequent row processing*/
for i in 1..tab.column.count loop
if tab.column(i).description.type in (
dbms_tf.type_number,
dbms_tf.type_varchar2
) then
/*Modifiable*/
tab.column(i).pass_through := FALSE;
/*Used in the PTF context*/
tab.column(i).for_read := TRUE;
/* Propagate column to the modified*/
modif_cols(new_col_cnt) := tab.column(i).description;
new_col_cnt := new_col_cnt + 1;
end if;
end loop;
/*Return the list of modified cols*/
return dbms_tf.describe_t(
new_columns => modif_cols
);
end;
procedure fetch_rows
/*Process rowset and replace nulls*/
as
rowset dbms_tf.row_set_t;
num_rows pls_integer;
in_col_vc2 dbms_tf.tab_varchar2_t;
in_col_num dbms_tf.tab_number_t;
new_col_vc2 dbms_tf.tab_varchar2_t;
new_col_num dbms_tf.tab_number_t;
begin
/*Get rows*/
dbms_tf.get_row_set(
rowset => rowset,
row_count => num_rows
);
for col_num in 1..rowset.count() loop
/*Loop through the columns*/
for rn in 1..num_rows loop
/*Calculate new values in the same row*/
/*Get column by index and nvl the value for return column*/
if rowset(col_num).description.type = dbms_tf.type_number then
dbms_tf.get_col(
columnid => col_num,
collection => in_col_num
);
new_col_num(rn) := nvl(in_col_num(rn), 0);
elsif rowset(col_num).description.type = dbms_tf.type_varchar2 then
dbms_tf.get_col(
columnid => col_num,
collection => in_col_vc2
);
new_col_vc2(rn) := nvl(in_col_vc2(rn), 'n');
end if;
end loop;
/*Put the modified column to the result*/
if rowset(col_num).description.type = dbms_tf.type_number then
dbms_tf.put_col(
columnid => col_num,
collection => new_col_num
);
elsif rowset(col_num).description.type = dbms_tf.type_varchar2 then
dbms_tf.put_col(
columnid => col_num,
collection => new_col_vc2
);
end if;
end loop;
end;
end pkg_nvl;
/
create function f_replace_nulls(tab in table)
/*Function to replace nulls using PTF*/
return table pipelined
row polymorphic using pkg_nvl;
/
with a as (
select
1 as id, 'q' as val_vc2, 1 as val_num
from dual
union all
select
2 as id, '' as val_vc2, null as val_num
from dual
union all
select
3 as id, ' ' as val_vc2, 0 as val_num
from dual
)
select
id
, a.val_num
, a.val_vc2
, n.val_num as val_num_repl
, n.val_vc2 as val_vc2_repl
from a
join f_replace_nulls(a) n
using(id)
ID | VAL_NUM | VAL_VC2 | VAL_NUM_REPL | VAL_VC2_REPL
-: | ------: | :------ | -----------: | :-----------
3 | 0 | | 0 |
2 | null | null | 0 | n
1 | 1 | q | 1 | q
db<>fiddle here
Use COALESCE or NVL and list the columns you want to apply them to:
SELECT COALESCE(col1, 'n') AS col1,
COALESCE(col2, 0) AS col2,
COALESCE(col3, 'n') AS col3
FROM table_name;
The way I understood the question, you actually want to modify table's contents. If that's so, you'll need dynamic SQL.
Here's an example; sample data first, with some numeric and character columns having NULL values:
SQL> desc test
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(20)
SALARY NUMBER
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 200
3 Foot 0
4 0
Procedure reads USER_TAB_COLUMNS, checking desired data types (you can add some more, if you want), composes the update statement and executes it:
SQL> declare
2 l_str varchar2(1000);
3 begin
4 for cur_r in (select column_name, data_type
5 from user_tab_columns
6 where table_name = 'TEST'
7 and data_type in ('CHAR', 'VARCHAR2')
8 )
9 loop
10 l_str := 'update test set ' ||
11 cur_r.column_name || ' = nvl(' || cur_r.column_name ||', ''n'')';
12 execute immediate l_str;
13 end loop;
14
15 --
16
17 for cur_r in (select column_name, data_type
18 from user_tab_columns
19 where table_name = 'TEST'
20 and data_type in ('NUMBER')
21 )
22 loop
23 l_str := 'update test set ' ||
24 cur_r.column_name || ' = nvl(' || cur_r.column_name ||', 0)';
25 execute immediate l_str;
26 end loop;
27 end;
28 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 n 200
3 Foot 0
4 n 0
SQL>
You can create a table macro to intercept the columns and replace them with nvl if they're a number or varchar2:
create or replace function replace_nulls (
tab dbms_tf.table_t
)
return clob sql_macro as
stmt clob := 'select ';
begin
for i in 1..tab.column.count loop
if tab.column(i).description.type = dbms_tf.type_number
then
stmt := stmt || ' nvl ( ' ||
tab.column(i).description.name || ', 0 ) ' ||
tab.column(i).description.name || ',';
elsif tab.column(i).description.type = dbms_tf.type_varchar2
then
stmt := stmt || ' nvl ( ' ||
tab.column(i).description.name || ', ''n'' ) ' ||
tab.column(i).description.name || ',';
else
stmt := stmt || tab.column(i).description.name || ',';
end if;
end loop;
stmt := rtrim ( stmt, ',' ) || ' from tab';
return stmt;
end;
/
with a (
aa1,aa2,aa3
) as (
select 1, '2hhhh', sysdate from dual
union all
select null, null, null from dual
)
select * from replace_nulls(a);
AA1 AA2 AA3
---------- ----- -----------------
1 2hhhh 27-JUN-2022 13:17
0 n <null>

Use Type as output parameter for procedure

I'm trying to return a type as output parameter, but i'm getting error :
[Error] Compilation (12: 27): PLS-00382: expression is of wrong type
Here is the code:
create or replace TYPE OBJ_DCP FORCE as OBJECT (
ID NUMBER,
xxx_PROFILES_ID NUMBER,
DCP_NAME VARCHAR2(500 BYTE),
L_R_NUMBER VARCHAR2(150 BYTE),
STREET VARCHAR2(150 BYTE)
};
and
create or replace TYPE T_DCP_TYPE as TABLE OF OBJ_DCP;
and
create or replace procedure get_dcp_profiles(p_id in number, dcp_array_var OUT T_DCP_TYPE) is
cursor c_dcp_profiles is
select *
from table1 ca -- table1 has same structure of OBJ_DCP
where ca.id = p_id;
i number := 1;
begin
for r in c_dcp_profiles loop
dcp_array_var(i) := r; -- this line is errored
i := i + 1;
end loop;
end get_dcp_profiles;
Types are OK.
Sample table:
SQL> SELECT * FROM table1 ORDER BY id;
ID XXX_PROFILES_ID D L S
---------- --------------- - - -
1 1 a b c
1 2 d e f
2 3 x y z
SQL>
Procedure, modified:
SQL> CREATE OR REPLACE PROCEDURE get_dcp_profiles (
2 p_id IN NUMBER,
3 dcp_array_var OUT T_DCP_TYPE)
4 IS
5 CURSOR c_dcp_profiles IS
6 SELECT *
7 FROM table1 ca -- table1 has same structure of OBJ_DCP
8 WHERE ca.id = p_id;
9
10 i NUMBER := 1;
11 l_arr T_DCP_TYPE := T_DCP_TYPE ();
12 BEGIN
13 FOR r IN c_dcp_profiles
14 LOOP
15 l_arr.EXTEND;
16 l_arr (i) :=
17 OBJ_DCP (id => r.id,
18 xxx_profiles_id => r.xxx_profiles_id,
19 dcp_name => r.dcp_name,
20 l_r_number => r.l_r_number,
21 street => r.street);
22 i := i + 1;
23 END LOOP;
24
25 dcp_array_var := l_arr;
26 END get_dcp_profiles;
27 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 l_tab t_dcp_type;
3 BEGIN
4 get_dcp_profiles (1, l_tab);
5
6 FOR i IN 1 .. l_tab.COUNT
7 LOOP
8 DBMS_OUTPUT.put_line (l_tab (i).id || ' ' || l_tab (i).dcp_name);
9 END LOOP;
10 END;
11 /
1 a
1 d
PL/SQL procedure successfully completed.
SQL>
You do not need to use cursors or to manually populate the collection.
If you declare the table as an object-derived table:
CREATE TABLE table1 OF OBJ_DCP;
Then you can simplify the procedure down to:
CREATE PROCEDURE get_dcp_profiles(
p_id IN number,
dcp_array_var OUT T_DCP_TYPE
)
IS
BEGIN
SELECT VALUE(ca)
BULK COLLECT INTO dcp_array_var
FROM table1 ca
WHERE ca.id = p_id;
END get_dcp_profiles;
/
For the sample data:
INSERT INTO table1 (id, xxx_Profiles_id, DCP_NAME, L_R_NUMBER, STREET)
SELECT 1, 42, 'ABC name', 'ABC number', 'ABC street' FROM DUAL UNION ALL
SELECT 1, 63, 'DEF name', 'DEF number', 'DEF street' FROM DUAL UNION ALL
SELECT 2, 12, 'GHI name', 'GHI number', 'GHI street' FROM DUAL;
Then:
DECLARE
dcps t_dcp_type;
BEGIN
get_dcp_profiles (1, dcps);
FOR i IN 1 .. dcps.COUNT LOOP
DBMS_OUTPUT.PUT_LINE (dcps(i).id || ' ' || dcps(i).dcp_name);
END LOOP;
END;
/
Outputs:
1 ABC name
1 DEF name
If you do not want to use an object-derived table then you can use:
CREATE OR REPLACE PROCEDURE get_dcp_profiles(
p_id IN number,
dcp_array_var OUT T_DCP_TYPE
)
IS
BEGIN
SELECT OBJ_DCP(id, xxx_profiles_id, dcp_name, l_r_number, street)
BULK COLLECT INTO dcp_array_var
FROM table1
WHERE id = p_id;
END get_dcp_profiles;
/
db<>fiddle here

pl/sql function to parse string to token in oracle

I have written this code
Select replace (upper(substr(div_no,1,3), ',' ','',''')
from dual
I want when user write div_no like this ('v0e200,q0e600') must return
v0e,q0e
If I have a string like 's05200 , s02700' I want to take first three characters, like 's05 ',' s02'
It seems you're looking for the translate() function :
SQL> select translate ('vwe200,qwe600', ',1234567890', ',')
2 from dual;
TRANSLA
-------
vwe,qwe
SQL>
So your revised test data clarifies the requirement. Here is a regex solution for extracting the first three characters from each segment of a string:
SQL> with testdata as (
2 select 'vwe200,qwe600' as str from dual union all
3 select 's05200 , e0300' as str from dual
4 )
5 select regexp_replace(str, '([a-z0-9]{3})([a-z0-9]*)(,?)','\1\3')
6 from testdata;
REGEXP_REPLACE(STR,'([A-Z0-9]{3})([A-Z0-9]*)(,?)','\1\3')
--------------------------------------------------------------------------------
vwe,qwe
s05 , e03
SQL>
If removing the trailing spaces is required it can be done by adjusting the regex pattern :
SQL> with testdata as (
2 select 'vwe200,qwe600' as str from dual union all
3 select 's05200 , e0300' as str from dual
4 )
5 select regexp_replace(str, '( *)([a-z0-9]{3})([a-z0-9 ]*)(,?)','\2\4')
6 from testdata;
REGEXP_REPLACE(STR,'([A-Z0-9]{3})([A-Z0-9]*)(,?)','\1\3')
--------------------------------------------------------------------------------
vwe,qwe
s05,e03
SQL>
Injecting additional quotes is a use for the replace() function:
SQL> select replace('s05 , e03', ',', ''',''') from dual;
REPLACE('S0
-----------
s05 ',' e03
SQL>
Use the output from the previous answer as the input for this one.
Oracle 12c Query:
with FUNCTION getFirst3CharsOfDelimitedList(
str IN VARCHAR2
) RETURN VARCHAR2
IS
val VARCHAR2(4000);
i INTEGER := 1;
occ INTEGER := 1;
BEGIN
val := SUBSTR( str, i, 3 );
LOOP
i := INSTR( str, ',', 1, occ );
EXIT WHEN i = 0;
LOOP
EXIT WHEN SUBSTR( str, i + 1, 1 ) <> ' ';
i := i + 1;
END LOOP;
val := val || ',' || SUBSTR( str, i + 1, 3 );
occ := occ + 1;
END LOOP;
RETURN val;
END getFirst3CharsOfDelimitedList;
testdata as (
select 'vwe200,qwe600' as str from dual union all
select 's05200 , e0300' as str from dual
)
SELECT getFirst3CharsOfDelimitedList( str ) FROM testdata;
Output:
GETFIRST3CHARSOFDELIMITEDLIST(STR)
----------------------------------
vwe,qwe
s05,e03

Split function in oracle to comma separated values with automatic sequence

Need Split function which will take two parameters, string to split and delimiter to split the string and return a table with columns Id and Data.And how to call Split function which will return a table with columns Id and Data. Id column will contain sequence and data column will contain data of the string.
Eg.
SELECT*FROM Split('A,B,C,D',',')
Result Should be in below format:
|Id | Data
-- ----
|1 | A |
|2 | B |
|3 | C |
|4 | D |
Here is how you could create such a table:
SELECT LEVEL AS id, REGEXP_SUBSTR('A,B,C,D', '[^,]+', 1, LEVEL) AS data
FROM dual
CONNECT BY REGEXP_SUBSTR('A,B,C,D', '[^,]+', 1, LEVEL) IS NOT NULL;
With a little bit of tweaking (i.e., replacing the , in [^,] with a variable) you could write such a function to return a table.
There are multiple options. See Split single comma delimited string into rows in Oracle
You just need to add LEVEL in the select list as a column, to get the sequence number to each row returned. Or, ROWNUM would also suffice.
Using any of the below SQLs, you could include them into a FUNCTION.
INSTR in CONNECT BY clause:
SQL> WITH DATA AS
2 ( SELECT 'word1, word2, word3, word4, word5, word6' str FROM dual
3 )
4 SELECT trim(regexp_substr(str, '[^,]+', 1, LEVEL)) str
5 FROM DATA
6 CONNECT BY instr(str, ',', 1, LEVEL - 1) > 0
7 /
STR
----------------------------------------
word1
word2
word3
word4
word5
word6
6 rows selected.
SQL>
REGEXP_SUBSTR in CONNECT BY clause:
SQL> WITH DATA AS
2 ( SELECT 'word1, word2, word3, word4, word5, word6' str FROM dual
3 )
4 SELECT trim(regexp_substr(str, '[^,]+', 1, LEVEL)) str
5 FROM DATA
6 CONNECT BY regexp_substr(str , '[^,]+', 1, LEVEL) IS NOT NULL
7 /
STR
----------------------------------------
word1
word2
word3
word4
word5
word6
6 rows selected.
SQL>
REGEXP_COUNT in CONNECT BY clause:
SQL> WITH DATA AS
2 ( SELECT 'word1, word2, word3, word4, word5, word6' str FROM dual
3 )
4 SELECT trim(regexp_substr(str, '[^,]+', 1, LEVEL)) str
5 FROM DATA
6 CONNECT BY LEVEL
Using XMLTABLE
SQL> WITH DATA AS
2 ( SELECT 'word1, word2, word3, word4, word5, word6' str FROM dual
3 )
4 SELECT trim(COLUMN_VALUE) str
5 FROM DATA, xmltable(('"' || REPLACE(str, ',', '","') || '"'))
6 /
STR
------------------------------------------------------------------------
word1
word2
word3
word4
word5
word6
6 rows selected.
SQL>
Using MODEL clause:
SQL> WITH t AS
2 (
3 SELECT 'word1, word2, word3, word4, word5, word6' str
4 FROM dual ) ,
5 model_param AS
6 (
7 SELECT str AS orig_str ,
8 ','
9 || str
10 || ',' AS mod_str ,
11 1 AS start_pos ,
12 Length(str) AS end_pos ,
13 (Length(str) - Length(Replace(str, ','))) + 1 AS element_count ,
14 0 AS element_no ,
15 ROWNUM AS rn
16 FROM t )
17 SELECT trim(Substr(mod_str, start_pos, end_pos-start_pos)) str
18 FROM (
19 SELECT *
20 FROM model_param MODEL PARTITION BY (rn, orig_str, mod_str)
21 DIMENSION BY (element_no)
22 MEASURES (start_pos, end_pos, element_count)
23 RULES ITERATE (2000)
24 UNTIL (ITERATION_NUMBER+1 = element_count[0])
25 ( start_pos[ITERATION_NUMBER+1] = instr(cv(mod_str), ',', 1, cv(element_no)) + 1,
26 end_pos[iteration_number+1] = instr(cv(mod_str), ',', 1, cv(element_no) + 1) ) )
27 WHERE element_no != 0
28 ORDER BY mod_str ,
29 element_no
30 /
STR
------------------------------------------
word1
word2
word3
word4
word5
word6
6 rows selected.
SQL>
You could also use DBMS_UTILITY package provided by Oracle. It provides various utility subprograms. One such useful utility is COMMA_TO_TABLE procedure, which converts a comma-delimited list of names into a PL/SQL table of names.
Read DBMS_UTILITY.COMMA_TO_TABLE
Oracle Setup:
CREATE OR REPLACE FUNCTION split_String(
i_str IN VARCHAR2,
i_delim IN VARCHAR2 DEFAULT ','
) RETURN SYS.ODCIVARCHAR2LIST DETERMINISTIC
AS
p_result SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST();
p_start NUMBER(5) := 1;
p_end NUMBER(5);
c_len CONSTANT NUMBER(5) := LENGTH( i_str );
c_ld CONSTANT NUMBER(5) := LENGTH( i_delim );
BEGIN
IF c_len > 0 THEN
p_end := INSTR( i_str, i_delim, p_start );
WHILE p_end > 0 LOOP
p_result.EXTEND;
p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, p_end - p_start );
p_start := p_end + c_ld;
p_end := INSTR( i_str, i_delim, p_start );
END LOOP;
IF p_start <= c_len + 1 THEN
p_result.EXTEND;
p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, c_len - p_start + 1 );
END IF;
END IF;
RETURN p_result;
END;
/
Query
SELECT ROWNUM AS ID,
COLUMN_VALUE AS Data
FROM TABLE( split_String( 'A,B,C,D' ) );
Output:
ID DATA
-- ----
1 A
2 B
3 C
4 D
If you need a function try this.
First we'll create a type:
CREATE OR REPLACE TYPE T_TABLE IS OBJECT
(
Field1 int
, Field2 VARCHAR(25)
);
CREATE TYPE T_TABLE_COLL IS TABLE OF T_TABLE;
/
Then we'll create the function:
CREATE OR REPLACE FUNCTION TEST_RETURN_TABLE
RETURN T_TABLE_COLL
IS
l_res_coll T_TABLE_COLL;
l_index number;
BEGIN
l_res_coll := T_TABLE_COLL();
FOR i IN (
WITH TAB AS
(SELECT '1001' ID, 'A,B,C,D,E,F' STR FROM DUAL
UNION
SELECT '1002' ID, 'D,E,F' STR FROM DUAL
UNION
SELECT '1003' ID, 'C,E,G' STR FROM DUAL
)
SELECT id,
SUBSTR(STR, instr(STR, ',', 1, lvl) + 1, instr(STR, ',', 1, lvl + 1) - instr(STR, ',', 1, lvl) - 1) name
FROM
( SELECT ',' || STR || ',' AS STR, id FROM TAB
),
( SELECT level AS lvl FROM dual CONNECT BY level <= 100
)
WHERE lvl <= LENGTH(STR) - LENGTH(REPLACE(STR, ',')) - 1
ORDER BY ID, NAME)
LOOP
IF i.ID = 1001 THEN
l_res_coll.extend;
l_index := l_res_coll.count;
l_res_coll(l_index):= T_TABLE(i.ID, i.name);
END IF;
END LOOP;
RETURN l_res_coll;
END;
/
Now we can select from it:
select * from table(TEST_RETURN_TABLE());
Output:
SQL> select * from table(TEST_RETURN_TABLE());
FIELD1 FIELD2
---------- -------------------------
1001 A
1001 B
1001 C
1001 D
1001 E
1001 F
6 rows selected.
Obviously you'd need to replace the WITH TAB AS... bit with where you would be getting your actual data from.
Credit Credit
Use this 'Split' function:
CREATE OR REPLACE FUNCTION Split (p_str varchar2) return sys_refcursor is
v_res sys_refcursor;
begin
open v_res for
WITH TAB AS
(SELECT p_str STR FROM DUAL)
select substr(STR, instr(STR, ',', 1, lvl) + 1, instr(STR, ',', 1, lvl + 1) - instr(STR, ',', 1, lvl) - 1) name
from
( select ',' || STR || ',' as STR from TAB ),
( select level as lvl from dual connect by level <= 100 )
where lvl <= length(STR) - length(replace(STR, ',')) - 1;
return v_res;
end;
You can't use this function in select statement like you described in question, but I hope you will find it still useful.
EDIT: Here are steps you need to do.
1. Create Object: create or replace type empy_type as object(value varchar2(512))
2. Create Type: create or replace type t_empty_type as table of empy_type
3. Create Function:
CREATE OR REPLACE FUNCTION Split (p_str varchar2) return sms.t_empty_type is
v_emptype t_empty_type := t_empty_type();
v_cnt number := 0;
v_res sys_refcursor;
v_value nvarchar2(128);
begin
open v_res for
WITH TAB AS
(SELECT p_str STR FROM DUAL)
select substr(STR, instr(STR, ',', 1, lvl) + 1, instr(STR, ',', 1, lvl + 1) - instr(STR, ',', 1, lvl) - 1) name
from
( select ',' || STR || ',' as STR from TAB ),
( select level as lvl from dual connect by level <= 100 )
where lvl <= length(STR) - length(replace(STR, ',')) - 1;
loop
fetch v_res into v_value;
exit when v_res%NOTFOUND;
v_emptype.extend;
v_cnt := v_cnt + 1;
v_emptype(v_cnt) := empty_type(v_value);
end loop;
close v_res;
return v_emptype;
end;
Then just call like this:
SELECT * FROM (TABLE(split('a,b,c,d,g')))
This function returns the nth part of input string MYSTRING.
Second input parameter is separator ie., SEPARATOR_OF_SUBSTR
and the third parameter is Nth Part which is required.
Note: MYSTRING should end with the separator.
create or replace FUNCTION PK_GET_NTH_PART(MYSTRING VARCHAR2,SEPARATOR_OF_SUBSTR VARCHAR2,NTH_PART NUMBER)
RETURN VARCHAR2
IS
NTH_SUBSTR VARCHAR2(500);
POS1 NUMBER(4);
POS2 NUMBER(4);
BEGIN
IF NTH_PART=1 THEN
SELECT REGEXP_INSTR(MYSTRING,SEPARATOR_OF_SUBSTR, 1, 1) INTO POS1 FROM DUAL;
SELECT SUBSTR(MYSTRING,0,POS1-1) INTO NTH_SUBSTR FROM DUAL;
ELSE
SELECT REGEXP_INSTR(MYSTRING,SEPARATOR_OF_SUBSTR, 1, NTH_PART-1) INTO POS1 FROM DUAL;
SELECT REGEXP_INSTR(MYSTRING,SEPARATOR_OF_SUBSTR, 1, NTH_PART) INTO POS2 FROM DUAL;
SELECT SUBSTR(MYSTRING,POS1+1,(POS2-POS1-1)) INTO NTH_SUBSTR FROM DUAL;
END IF;
RETURN NTH_SUBSTR;
END;
Hope this helps some body, you can use this function like this in a loop to get all the values separated:
SELECT REGEXP_COUNT(MYSTRING, '~', 1, 'i') INTO NO_OF_RECORDS FROM DUAL;
WHILE NO_OF_RECORDS>0
LOOP
PK_RECORD :=PK_GET_NTH_PART(MYSTRING,'~',NO_OF_RECORDS);
-- do some thing
NO_OF_RECORDS :=NO_OF_RECORDS-1;
END LOOP;
Here NO_OF_RECORDS,PK_RECORD are temp variables.
Hope this helps.
Best Query For comma separated
in This Query we Convert Rows To Column ...
SELECT listagg(BL_PRODUCT_DESC, ', ') within
group( order by BL_PRODUCT_DESC) PROD
FROM GET_PRODUCT
-- WHERE BL_PRODUCT_DESC LIKE ('%WASH%')
WHERE Get_Product_Type_Id = 6000000000007
Created PL/SQL function that can split string by specified delimiter and return result as VARRAY.
CREATE OR REPLACE FUNCTION split(p_parameters VARCHAR2, p_delimiter VARCHAR2) RETURN string_varray AS
v_delimiter_position NUMBER := 0;
v_read_position NUMBER :=1;
v_list string_varray := string_varray();
v_substring VARCHAR2(4000);
FUNCTION normalize(v_substring VARCHAR2, p_delimiter VARCHAR2) RETURN VARCHAR2 AS
BEGIN
RETURN trim(TRAILING p_delimiter FROM trim(BOTH ' ' FROM v_substring));
END normalize;
BEGIN
LOOP
v_delimiter_position := instr(p_parameters, p_delimiter, v_read_position);
IF v_delimiter_position = 0 THEN
v_delimiter_position := LENGTH(p_parameters);
END IF;
v_substring := substr(p_parameters, v_read_position, v_delimiter_position-v_read_position+1);
v_list.EXTEND;
v_list(v_list.LAST) := normalize(v_substring, p_delimiter);
v_read_position := v_delimiter_position+1;
IF v_delimiter_position = LENGTH(p_parameters) THEN
EXIT;
END IF;
END LOOP;
RETURN v_list;
END split;
string_varray is VARRAY of VARCHAR2(4000) type. Function also removes whitespaces and the start and end of each value. Invocation example:
select * from table(split('zaa, dddd,ccc', ','));
Will produce three rows in output: zaa dddd ccc
begin
for rec in (select * from table(split('shfgjsdfg,242535', ',')))
loop
dbms_output.put_line(rec.COLUMN_VALUE);
end loop;
end;
-- Output
shfgjsdfg
242535
Try like below
select
split.field(column_name,1,',','"') name1,
split.field(column_name,2,',','"') name2
from table_name

split string into multiple rows with multiple columns

I have a string with user information. I have to write a function that takes string as input and inserts into some table.
Input string contains multiple rows with multiple columns like following:
inputString= "1,cassey,1222,12-12-12:2,timon,,02-02-12:3,john,3333,03-03-12"
what i want is to create insert from this...
How it can be achieved?
Solution in a single Query as following:
But I replaced the ',,' with 'NULL'
inputString= "1,cassey,1222,12-12-12:2,timon,NULL,02-02-12:3,john,3333,03-03-12"
SELECT REGEXP_SUBSTR (REGEXP_SUBSTR (inputString, '[^:]+', 1, LEVEL), '[^,]+', 1, 1) AS Col1,
REGEXP_SUBSTR (REGEXP_SUBSTR (inputString, '[^:]+', 1, LEVEL), '[^,]+', 1, 2) AS Col2,
REGEXP_SUBSTR (REGEXP_SUBSTR (inputString, '[^:]+', 1, LEVEL), '[^,]+', 1, 3) AS Col3,
REGEXP_SUBSTR (REGEXP_SUBSTR (inputString, '[^:]+', 1, LEVEL), '[^,]+', 1, 4) AS Col4
FROM DUAL
CONNECT BY LEVEL <= LENGTH (REGEXP_REPLACE (inputString, '[^:]+')) + 1;
Result:
Col1 Col2 Col3 Col4
------ ------ ------ ------
1 cassey 1222 12-12-12
2 timon NULL 02-02-12
3 john 3333 03-03-12
3 rows selected.
Package Spec:
CREATE OR REPLACE PACKAGE string_conversion AS
TYPE string_test_tab IS TABLE OF VARCHAR2(2000);
TAB string_test_tab;
PROCEDURE string_convert(v_input IN VARCHAR2, v_output OUT string_test_tab);
END string_conversion;
/
Package Body:
CREATE OR REPLACE PACKAGE BODY string_conversion AS
PROCEDURE string_convert(v_input IN VARCHAR2, v_output OUT string_test_tab) IS
v_index NUMBER := 1;
v_index_comma NUMBER := 1;
TAB2 string_test_tab;
v_input_str VARCHAR2(2000):= v_input||',';
BEGIN
v_output := string_test_tab();
LOOP
v_index_comma := INSTR (v_input_str, ',',v_index);
EXIT WHEN v_index_comma = 0;
v_output.extend();
v_output(v_output.count):= SUBSTR(v_input_str, v_index, v_index_comma - v_index);
dbms_output.put_line(v_output(v_output.count));
v_index := v_index_comma + 1;
END LOOP;
END string_convert;
END string_conversion;
/
Testing:
DECLARE
v_out1 string_conversion.string_test_tab;
BEGIN
string_conversion.string_convert('a,b,c,d,e',v_out1);
FOR j IN v_out1.FIRST .. v_out1.LAST
LOOP
dbms_output.put_line(v_out1(j));
END LOOP;
END;
/
OUTPUT:
a
b
c
d
e
DECLARE
v_string VARCHAR2(20) := 'a,b,c,d,e';
v_val VARCHAR2(2000);
BEGIN
dbms_output.put_line('v_string = '||v_string);
LOOP
v_val := SUBSTR(v_string,1, INSTR(v_string, ',', 1)-1);
IF INSTR(v_string, ',', 1) = 0 THEN
v_val := v_string;
END IF;
dbms_output.put_line(v_val);
EXIT WHEN INSTR(v_string, ',', 1) = 0;
v_string := SUBSTR(v_string,INSTR(v_string, ',', 1)+1);
END LOOP;
END;

Resources