How to get a data between vertical bar using PL/SQL - sql

Table DATA:
id text
1 CLARE|FEMALE|PRESIDENT
2 SCARLET|FEMALE|MANAGER
3 FRANK|MALE|ANALYST
...
What I need to do is to get all data between vertical bar and put it in my variables(for insert to another table),
here what i tried :
DECLARE
v_NAME varchar2(100);
v_GENDER nvarchar2(100);
v_POSITION nvarchar2(100);
CURSOR c1
IS
SELECT *
FROM DATA
ORDER BY id;
BEGIN
FOR x IN c1 LOOP
v_NAME := REGEXP_SUBSTR(x.TEXT_NOTE, '[^|]+', 1, 1);
v_GENDER := REGEXP_SUBSTR(x.TEXT_NOTE, '[^|]+', 1, 2);
v_POSITION := REGEXP_SUBSTR(x.TEXT_NOTE, '[^|]+', 1, 3);
DBMS_OUTPUT.PUT_LINE(v_NAME ,v_GENDER ,v_POSITION); --test
END LOOP;
END;
Obviously I'm not doing this right as it doesn't work. Can somebody suggest a solution?
Thanks!

I think, there are two points to fix in your attempt :
In your LOOP you are referring to column "x.TEXT_NOTE" whereas it should be column "TEXT" from your table DATA.
You need to change that output line DBMS_OUTPUT.PUT_LINE(v_NAME ,v_GENDER ,v_POSITION) by DBMS_OUTPUT.PUT_LINE(v_NAME||', '||v_GENDER||', '||v_POSITION)
CREATE TABLE DATA_TO_SPLIT (id, text) as (
select 1, 'CLARE|FEMALE|PRESIDENT' from dual union all
select 2, 'SCARLET|FEMALE|MANAGER' from dual union all
select 3, 'FRANK|MALE|ANALYST' from dual
)
;
DECLARE
v_NAME varchar2(100);
v_GENDER nvarchar2(100);
v_POSITION nvarchar2(100);
CURSOR c1
IS
SELECT *
FROM DATA_TO_SPLIT
ORDER BY id;
BEGIN
FOR x IN c1 LOOP
v_NAME := REGEXP_SUBSTR(x.TEXT, '[^|]+', 1, 1);
v_GENDER := REGEXP_SUBSTR(x.TEXT, '[^|]+', 1, 2);
v_POSITION := REGEXP_SUBSTR(x.TEXT, '[^|]+', 1, 3);
DBMS_OUTPUT.PUT_LINE(v_NAME||', '||v_GENDER||', '||v_POSITION); --test
END LOOP;
END;
/

The string_split function should work.
SELECT * FROM STRING_SPLIT('CLARE|FEMALE|PRESIDENT', '|')

Indeed no problem seems with the current code except
the typo which needs column name conversion from text_note to
text
and adding an Insert statement for a presumed table (namely
data2) is missing
Btw, current DBMS_OUTPUT.PUT_LINEs syntax is wrong
So, use the following code :
DECLARE
v_name VARCHAR2(100);
v_gender NVARCHAR2(100);
v_position NVARCHAR2(100);
CURSOR c1 IS
SELECT *
FROM data
ORDER BY id;
BEGIN
FOR x IN c1
LOOP
v_name := TRIM(REGEXP_SUBSTR(x.text, '[^|]+', 1, 1));
v_gender := TRIM(REGEXP_SUBSTR(x.text, '[^|]+', 1, 2));
v_position := TRIM(REGEXP_SUBSTR(x.text, '[^|]+', 1, 3));
DBMS_OUTPUT.PUT_LINE(v_name||' - '||v_gender||' - '||v_position);
INSERT INTO data2
VALUES(v_name,v_gender,v_position);
END LOOP;
COMMIT;
END;
/
where TRIM() function stands for removing potential leading and trailing whitespaces
Demo

Related

select in a loop in oracle sql?

My stored procedure is used for getting string values of ids separate by ',' from a string contains names in table AUTH_GROUPS.
for example:
id name
1 role_1
2 role_2
3 role_3
the input is: 'role_1,role_2,role_3'
the output is: '1,2,3'
CREATE OR REPLACE PROCEDURE PROCEDURE1(P_ERROR_MSG OUT VARCHAR2, P_ROLE_STRING IN VARCHAR2 ) AS
BEGIN
DECLARE
lvOutPut VARCHAR2(2000);
vId varchar2(1000);
BEGIN
lvOutPut := '';
FOR i IN
(SELECT trim(regexp_substr(P_ROLE_STRING, '[^,]+', 1, LEVEL)) l
FROM dual
CONNECT BY LEVEL <= regexp_count(P_ROLE_STRING, ',')+1
)
LOOP
select id into vId from AUTH_GROUPS where NAME = i; --this line got error 'expression is of wrong type'
lvOutPut := lvOutPut || vId || ',';
END LOOP;
P_ERROR_MSG := lvOutPut;
P_ERROR_MSG := substr(P_ERROR_MSG, 1, LENGTH(P_ERROR_MSG) - 1);
END;
END PROCEDURE1;
But it has an error in the line that I commented. I tried i.1 or i.value but still got errors.
You need to use the actual column name. i is loop handle name.
....
BEGIN
lvOutPut := '';
FOR i IN
(SELECT trim(regexp_substr(P_ROLE_STRING, '[^,]+', 1, LEVEL)) l -- this is column name to be used inside the loop
FROM dual
CONNECT BY LEVEL <= regexp_count(P_ROLE_STRING, ',')+1
)
LOOP
select id into vId from AUTH_GROUPS where NAME = i.l; -- change here
...
/* Formatted on 6/23/2020 2:08:34 PM (QP5 v5.354) */
CREATE OR REPLACE PROCEDURE PROCEDURE1 (P_ERROR_MSG OUT VARCHAR2,
P_ROLE_STRING IN VARCHAR2)
AS
BEGIN
DECLARE
lvOutPut VARCHAR2 (2000);
vId VARCHAR2 (1000);
BEGIN
lvOutPut := '';
FOR i IN ( SELECT TRIM (REGEXP_SUBSTR (P_ROLE_STRING,
'[^,]+',
1,
LEVEL)) l
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT (P_ROLE_STRING, ',') + 1)
LOOP
SELECT id
INTO vId
FROM AUTH_GROUPS
WHERE NAME = i.l; --this line got error 'expression is of wrong type'
lvOutPut := lvOutPut || vId || ',';
END LOOP;
P_ERROR_MSG := lvOutPut;
P_ERROR_MSG := SUBSTR (P_ERROR_MSG, 1, LENGTH (P_ERROR_MSG) - 1);
END;
END PROCEDURE1;

Extracting values from a pipe separated string

I have a string which looks like --
12361_BBMS_GTECHL|12362_BBMS_PRIM|12363_BBMS_SEC|....and so on
So i need to fetch
12361 and BBMS_GTECHL
12362 and BBMS_PRIM
12363 and BBMS_SEC
i used --
select *
FROM
TABLE(XMLSEQUENCE(
EXTRACT(
XMLTYPE('<rowset><row><Code>'||
replace(replace('12361=BBMS_GTECHL|12362=BBMS_PRIM','|','</Value></row><row><Code>'),'=','</Code><Value>')||'</Value>'||'</row></rowset>'),'/rowset/row')));
declare
l_val varchar2(1000);
begin
select substr('12361_BBMS_GTECHL|12362_BBMS_PRIM', instr('|')+1) into l_val from dual;
dbms_output.put_line(l_val);
end;
But getting problem in getting desired result !
I have need to write this logic in a package that i will do if i got some hint here.
My DB version is --
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
Here is a solution using a recursive factored subquery ("recursive CTE"). Note the use of pointers to the location of pipe symbols and the first underscore after each pipe (disregarding the other underscores). Also, the solution uses only standard INSTR and SUBSTR, avoiding the use of regular expressions (which perform somewhat slower - important if you process lots of data).
with input_data (input_str) as (
select '12361_BBMS_GTECHL|12362_BBMS_PRIM|12363_BBMS_SEC' from dual
),
t (str) as (
select '|' || input_str || '|' from input_data
),
r (lvl, code, descr, str, p1_from, p2_from, p1_to, p2_to) as (
select 0, null, null, str, 1, 1, instr(str, '_', 1, 1), instr(str, '|', 1, 2)
from t
union all
select lvl+1, substr(str, p2_from + 1, p1_to - p2_from - 1),
substr(str, p1_to + 1, p2_to - p1_to - 1),
str, p1_to, p2_to, instr(str, '_', p2_to + 1, 1),
instr(str, '|', p2_to + 1, 1)
from r
where p1_to != 0
)
select code, descr
from r
where lvl != 0;
Output:
CODE DESCR
------- --------------------
12361 BBMS_GTECHL
12362 BBMS_PRIM
12363 BBMS_SEC
If I were you and my primary consideration was performance i would use Table Functions. mathguys solution works perfectly but it will be more performant if we use a pipelined function.
First we create types which are necessary for our function.
drop type type_test_table;
drop type type_test_row;
CREATE TYPE type_test_row AS OBJECT (
code varchar2(2000),
descr VARCHAR2(50)
)
/
CREATE TYPE type_test_table IS TABLE OF type_test_row
/
Then we create our function :
create or replace function test_pipe_func return type_test_table pipelined as
cursor c_data_in is
select '12361'||level||'_BBMS_GTECHL'||level||'|12362'||level||'_BBMS_PRIM'||level||'|12363'||level||'_BBMS_SEC'||level||'|12364'||level||'_BBU_SEC'||level as str from dual
connect by level <= 1000000;
v_element varchar2(300);
v_code varchar2(100);
v_descr varchar2(200);
p_deb number;
p_fin number;
begin
for l_data_in in c_data_in loop
p_deb := 0;
p_fin := 1;
while p_fin > 0 loop
p_fin := case when p_deb = 0 then instr(l_data_in.str,'|',1, 1) else instr(l_data_in.str,'|',p_deb-1, 2) end;
p_deb := case when p_deb = 0 then 1 else instr(l_data_in.str,'|',p_deb-1, 1)+1 end;
v_element := case when p_fin = 0 then substr(l_data_in.str, p_deb) else substr(l_data_in.str, p_deb, p_fin - p_deb) end;
p_deb := p_fin +1;
v_code := substr(v_element, 1 , instr(v_element, '_' , 1,1)-1);
v_descr := substr(v_element, instr(v_element, '_' , 1,1)+1);
pipe row(type_test_row(v_code, v_descr));
end loop;
end loop;
end test_pipe_func;
/
I changed the test case a little bit to be able to generate as many lines as necessary for my tests. And i used a pipelined function to limit usage of Process memory in case of big datasets and to be able to use it with a select. If your use case is different(i don't know maybe to insert into a table using the input) another option can be to use bulk collect into and forall.
create or replace procedure test_bulk_collect_proc as
cursor c_data_in is
select '12361'||level||'_BBMS_GTECHL'||level||'|12362'||level||'_BBMS_PRIM'||level||'|12363'||level||'_BBMS_SEC'||level as str from dual
connect by level <= 1000000;
type type_table_data_in is table of c_data_in%rowtype;
table_data_in type_table_data_in;
v_element varchar2(300);
v_code varchar2(100);
v_descr varchar2(200);
p_deb number;
p_fin number;
v_str varchar2(4000);
v_t_insr type_test_table;
limit_in number := 100000;
i number;
begin
OPEN c_data_in;
LOOP
FETCH c_data_in BULK COLLECT INTO table_data_in LIMIT limit_in;
v_t_insr := type_test_table();
i := 1;
for indx IN 1 .. table_data_in.COUNT LOOP
v_str := table_data_in(indx).str;
p_deb := 0;
p_fin := 1;
while p_fin > 0 loop
p_fin := case when p_deb = 0 then instr(v_str,'|',1, 1) else instr(v_str,'|',p_deb-1, 2) end;
p_deb := case when p_deb = 0 then 1 else instr(v_str,'|',p_deb-1, 1)+1 end;
v_element := case when p_fin = 0 then substr(v_str, p_deb) else substr(v_str, p_deb, p_fin - p_deb) end;
p_deb := p_fin +1;
v_code := substr(v_element, 1 , instr(v_element, '_' , 1,1)-1);
v_descr := substr(v_element, instr(v_element, '_' , 1,1)+1);
v_t_insr.extend;
v_t_insr(i) := type_test_row(v_code, v_descr);
i:= i+1;
end loop;
END LOOP;
forall t in v_t_insr.first..v_t_insr.last
insert into test_bbu(CODE, DESCR) values (v_t_insr(t).code, v_t_insr(t).descr);
EXIT WHEN table_data_in.COUNT < limit_in;
END LOOP;
End;
/
I tested all three methods on my database. To test the sql of mathguy and the pipelined function i used CTAS and for the bulk collect into i simply executed the procedure.
create table test_bbu as
with input_data (input_str) as (
select '12361'||level||'_BBMS_GTECHL'||level||'|12362'||level||'_BBMS_PRIM'||level||'|12363'||level||'_BBMS_SEC'||level from dual
connect by level <= 1000000
),
t (str) as (
select '|' || input_str || '|' from input_data
),
r (lvl, code, descr, str, p1_from, p2_from, p1_to, p2_to) as (
select 0, null, null, str, 1, 1, instr(str, '_', 1, 1), instr(str, '|', 1, 2)
from t
union all
select lvl+1, substr(str, p2_from + 1, p1_to - p2_from - 1),
substr(str, p1_to + 1, p2_to - p1_to - 1),
str, p1_to, p2_to, instr(str, '_', p2_to + 1, 1),
instr(str, '|', p2_to + 1, 1)
from r
where p1_to != 0
)
select code, descr
from r
where lvl != 0;
create table test_bbu2 as
select * from table(test_pipe_func);
execute test_bulk_collect_proc;
I tested three methods with 500K and 1M lines. Here are my results but i urge you to test on your environnement before you make your decision.
500K 1M
----------------------------------------
SQL 36s 1m:15s
Pipelined 11s 23s
Bulk Collect 8s 17s

Alias for a table of type?

I have the following definition for a table type
create table number_table as table of number;
And I'd like to use it like in the following example.
declare
l_myTable number_table := number_table(1, 2, 3);
begin
for i in (select * from l_myTable) loop
dbms_output.put_line(i.???); -- how do I reference the numbers here?
end loop;
end;
Forgive my code being somewhat pointless, how would I reference that i.??? though to get the number out of the iterator?
Just slightly change your query to
select rownum, column_value from l_myTable
and then use dbms_output.put_line(i.rownum); as an index and dbms_output.put_line(i.column_value ); as a value
create type number_table as table of number;
/
You do not need to use a Cursor FOR LOOP as you can just iterate over the collection:
declare
l_myTable number_table := number_table(3, 2, 1);
begin
for i in 1 .. l_myTable.COUNT loop
dbms_output.put_line( l_myTable(i) );
end loop;
end;
/
However, if there is some reason why you need to use a cursor then you can use the ROWNUM pseudocolumn to get index within the collection and the COLUMN_VALUE pseudocolumn to get the value at that index in the collection using the query:
SELECT ROWNUM, COLUMN_VALUE FROM TABLE( l_myTable )
Like this:
declare
l_myTable number_table := number_table(3, 2, 1);
begin
for i in ( SELECT ROWNUM, COLUMN_VALUE FROM TABLE( l_myTable ) ) loop
dbms_output.put_line( i.rownum || ': ' || i.column_value ); -- how do I reference the numbers here?
end loop;
end;
/

Wants numeric data from varchar2 datatype column

I have a table Product with a varchar2 datatype of column name Value, in this column values are stored as
All,10:23,0.84522,1.245,10:54:68,
All,1:22:00,0.245,45:12:00
etc.
We have to extract all the floating values like (0.84522,1.245,0.245) and ones that ends with ":00" like (1:22:00,45:12:00).
I have following query, but it doesn't seems to work; it gives me all the values except characters.
select * from Product where Values BETWEEN to_char (0) and to_char (2);
I think this would work
select *
FROM Product
WHERE
(Value LIKE '%:00' AND Value<> 'ALL') AND (Value BETWEEN to_NUMBER (0) and to_NUMBER (2))
Try this query:
select *
from (select distinct regexp_substr(t.value, '[^,]+', 1, level) phrase
from Product t
connect by regexp_substr(t.value, '[^,]+', 1, level) is not null) ph
where regexp_like(ph.phrase, '(\d+\.\d+)|(.+:00)')
The regular expression in the where clause may need some tunning
What it does is-
seperates all phrases (the inner query)
selects only those that matches your criteria
UPDATE
If you suffer from performance you can try a different approach:
create or replace type phrase_typ is object
(
phrase varchar2(100)
)
;
/
create or replace type phrase_tab as table of phrase_typ;
/
create or replace function split_string(del in varchar2) return phrase_tab
pipelined is
phrase varchar2(1000);
str_t varchar2(1000);
v_del_i number;
cursor c is with t as
select value from product;
begin
for r in c loop
str_t := r.value;
while str_t is not null loop
v_del_i := instr(str_t, del, 1, 1);
if v_del_i = 0 then
phrase := str_t;
str_t := '';
else
phrase := substr(str_t, 1, v_del_i - 1);
str_t := substr(str_t, v_del_i + 1);
end if;
if regexp_like(phrase, '(\d+\.\d+)|(.+:00)') then
pipe row(phrase_typ(phrase));
end if;
end loop;
end loop;
return;
end split_string;
/
Now your query should look like this:
select * from table(split_string(','))

Executing large query and returning rows in Oracle

I have a function that receives a query as parameter (as clob type) and 'selects' this query's rows for returning. I need to use dbms_sql, because the query's size is larger than 32kb (~150kb).
I'm stuck at point of fetching into result:
-- execute immediate style (does not work with clob):
EXECUTE IMMEDIATE large_query BULK COLLECT INTO V_TAB ;
-- dbms_sql style:
v_upperbound := CEIL(DBMS_LOB.GETLENGTH(large_query)/256);
FOR i IN 1..v_upperbound
LOOP
v_sql(i) := DBMS_LOB.SUBSTR(large_query,256,((i-1)*256)+1);
END LOOP;
v_cur := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(v_cur, v_sql, 1, v_upperbound, FALSE, DBMS_SQL.NATIVE);
v_ret := DBMS_SQL.EXECUTE(v_cur);
-- NOW WHAT??
I'm in Oracle 9i/10g, so I can't use dbms_slq.to_refcursor.
Any suggestions?
Here's an example from the Oracle docs. Basically you need dbms_sql.fetch_rows and dbms_sql.column_value:
CREATE TABLE multi_tab (num NUMBER,
dat1 DATE,
var VARCHAR2(24),
dat2 DATE)
declare
c NUMBER;
d NUMBER;
n_tab DBMS_SQL.NUMBER_TABLE;
d_tab1 DBMS_SQL.DATE_TABLE;
v_tab DBMS_SQL.VARCHAR2_TABLE;
d_tab2 DBMS_SQL.DATE_TABLE;
indx NUMBER := 10;
BEGIN
c := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(c, 'select * from multi_tab order by 1', DBMS_SQL.NATIVE);
DBMS_SQL.DEFINE_ARRAY(c, 1, n_tab, 5, indx);
DBMS_SQL.DEFINE_ARRAY(c, 2, d_tab1, 5, indx);
DBMS_SQL.DEFINE_ARRAY(c, 3, v_tab, 5, indx);
DBMS_SQL.DEFINE_ARRAY(c, 4, d_tab2, 5, indx);
d := DBMS_SQL.EXECUTE(c);
loop
d := DBMS_SQL.FETCH_ROWS(c);
DBMS_SQL.COLUMN_VALUE(c, 1, n_tab);
DBMS_SQL.COLUMN_VALUE(c, 2, d_tab1);
DBMS_SQL.COLUMN_VALUE(c, 3, v_tab);
DBMS_SQL.COLUMN_VALUE(c, 4, d_tab2);
EXIT WHEN d != 5;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(c);
Thats what I did based on the answer:
-- open cursor and execute the query
v_cur := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(v_cur, v_sql, 1, v_upperbound, FALSE, DBMS_SQL.NATIVE);
v_ret := DBMS_SQL.EXECUTE(v_cur);
-- define a column to receive the result (in my case, it's a single varchar2 column)
dbms_sql.define_column(v_cur, 1, V_ROW, 4000);
-- initialize de result table ( TABELA_REGISTROS_TYPE is a table of varchar2(4000) )
v_tab := TABELA_REGISTROS_TYPE();
-- initializa a counter
v_d := 1;
-- loop through result rows
LOOP
-- fetch a row into a varchar2(4000) variable
EXIT WHEN DBMS_SQL.FETCH_ROWS(v_cur) = 0;
DBMS_SQL.COLUMN_VALUE(v_cur, 1, V_ROW);
-- append that row into result set
V_TAB.EXTEND();
V_TAB(v_d) := v_row;
v_d := v_d + 1;
END LOOP;
-- finally close the cursor
DBMS_SQL.close_cursor(v_cur);
-- then return the result set
RETURN V_TAB;