How we can get the value from string: 1;BWQTY;4|2;NGRID;34664 and store into a variable if any specific SUBSTRING found? - oracle

I have a table name as event_creation_template which having column as TEXT ,which contains value like below :
1;BWQTY;4|2;NGRID;34664
1;DTYPE;Amount|2;LOYAL;R
How we can get the value from string:
1;BWQTY;4|2;NGRID;34664
If string is having BWQTY then store 4 into variable again search for the NGRID ,if string contains NGRID , we need in 34664 into different variable.
using Oracle PL/SQL.

considering the string like 'BWQTY' will always lie between two non-aphanumeric characters and the corresponding values like 4 lies between either non-alphanumeric character or appears at the end like in the two examples above you can use below code
CREATE OR REPLACE PR_PREP
IS
V_VAR1 VARCHAR2(100);
V_-VAR2 VARCHAR2(100);
V_SEARCH_VAL1 VARCHAR2(100) := 'BWQTY';
V_SEARCH_VAL2 VARCHAR2(100) := 'NGRID';
CURSOR C1
IS
SELECT TEXT FROM EVENT_CREATION_TEMPLATE;
BEGIN
FOR REC IN C1
LOOP
IF INSTR(REC.TEXT,V_SEARCH_VAL1) > 0 THEN
SELECT NVL(SUBSTR(SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1), 1, REGEXP_INSTR( SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1), '[^0-9a-zA-Z]',1)-1), SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1))
INTO V_VAR1
FROM DUAL ;
END IF;
IF INSTR(REC.TEXT,V_SEARCH_VAL2) > 0 THEN
SELECT NVL(SUBSTR(SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1), 1, REGEXP_INSTR( SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1), '[^0-9a-zA-Z]',1)-1), SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1))
INTO V_VAR2
FROM DUAL ;
END IF;
-- DO FURTHER WORK
END LOOP;
END;

You can use regular expressions
for BWQTY:
select str,
SUBSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'),
INSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'), ';') + 1 ,
LENGTH(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)')) - INSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'), ';')) AS VALUE
from test
for NGRID:
select str,
SUBSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'),
INSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'), ';') + 1 ,
LENGTH(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)')) - INSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'), ';')) AS VALUE
from test
Where str is your input string...

Related

Flag duplicate characters in a string

I'm using oracle apex and i'm trying to write a pl/sql statement that will flag duplicates in a string. For example the string 'P,T,P,C' has two occurrences of the letter 'P' so it should raise an error. After all my digging the closest I got to achieving this was by using REGEXP_LIKE, but my regular expression skills are sub par. If anyone can assist that would be much appreciated.
DECLARE
v_seccode varchar2(10) := 'P,T,P,C';
BEGIN
if regexp_like(v_seccode, '(P{2,}?|C{2,}?|W{2,}?|T{2,}?)') then
raise_application_error(-20001,'You can not have the same SEC code listed more than once!');
end if;
END;
If your apex version is relatively recent you have access to the APEX_STRING API. Here is some code using that API, using same logic #Littlefoot showed in the other answer:
DECLARE
l_arr1 apex_t_varchar2;
l_arr2 apex_t_varchar2;
BEGIN
-- convert string to array with "," delimiter
l_arr1 := apex_string.split(:P1_SECCODE,',');
l_arr2 := l_arr1;
-- remove dupes from l_arr2
l_arr2 := l_arr2 MULTISET UNION DISTINCT l_arr2;
IF l_arr2 != l_arr1 THEN
return 'You can not have the same SEC code listed more than once!';
END IF;
END;
/
As you're using Apex, I suggest you create a validation within it. Code you posted suggests that you'd want to handle that elsewhere (process? Database trigger?).
Presuming that page item name is P1_SECCODE, validation - a PL/SQL function that returns error text - might look like this (read comments within code):
declare
l_seccode varchar2(20);
begin
-- Split P1_SECCODE into rows, fetch distinct values and aggregate them back.
-- If P1_SECCODE = 'P,T,P,C' then the L_SECCODE = 'P,T,C'
select listagg(distinct regexp_substr(:P1_SECCODE, '[^,]+', 1, level), ',')
within group (order by null)
into l_seccode
from dual
connect by level <= regexp_count(:P1_SECCODE, ',') + 1;
-- Now compare whether P1_SECCODE has the same number of commas as L_SECCODE.
-- If not, it means that P1_SECCODE contained duplicates so - return an error message
if regexp_count(:P1_SECCODE, ',') <> regexp_count(l_seccode, ',') then
return 'You can not have the same SEC code listed more than once!';
end if;
end;
If your database version doesn't support distinct within listagg, then first fetch distinct values and then aggregate them:
declare
l_seccode varchar2(20);
begin
-- Split P1_SECCODE into rows, fetch distinct values and aggregate them back.
-- If P1_SECCODE = 'P,T,P,C' then the L_SECCODE = 'P,T,C'
with temp as
(select distinct regexp_substr(:P1_SECCODE, '[^,]+', 1, level), ',') val
from dual
connect by level <= regexp_count(:P1_SECCODE, ',') + 1
)
select listagg(val, ',') within group (order by null)
into l_seccode
from temp;
-- Now compare whether P1_SECCODE has the same number of commas as L_SECCODE.
-- If not, it means that P1_SECCODE contained duplicates so - return an error message
if regexp_count(:P1_SECCODE, ',') <> regexp_count(l_seccode, ',') then
return 'You can not have the same SEC code listed more than once!';
end if;
end;
Given that your string can only have 4 possible letters PCWT, you could just use LIKE expressions along with a regular query:
SELECT *
FROM yourTable
WHERE string NOT LIKE '%P%P%' AND
string NOT LIKE '%C%C%' AND
string NOT LIKE '%W%W%' AND
string NOT LIKE '%T%T%';

PLSQL Function to sort string that's given as parameter

following issue:
I get a String as such "512, 986, 571, 665" transferred as parameter, basically a set of 3 digit numbers and these need to be returned sorted from highest to lowest.
I have only found answers pertaining such an issue when the String is found in a table and not given as parameter to a function
Here's one option: split input string into rows (that's what subquery in lines #4 - 6 does), and then aggregate them back using listagg with appropriate order by clause (line #3).
SQL> with test (col) as
2 (select '512, 986, 571, 665' from dual)
3 select listagg(val, ', ') within group (order by val) result
4 from (select to_number(trim(regexp_substr(col, '[^,]+', 1, level))) val
5 from test
6 connect by level <= regexp_count(col, ',') + 1
7 );
RESULT
--------------------------------------------------------------------------------
512, 571, 665, 986
SQL>
A programmatic approach would be to use each element in the list as the index of an associative array (so integers only, or it'll break), which will have the effect of sorting it, then loop through the resulting array constructing a new list:
create or replace function sort_number_list
( p_list varchar2 )
return varchar2
as
type aa is table of number index by pls_integer;
sort_tab aa;
sorted_list varchar2(4000);
begin
for i in 1..regexp_count(p_list, ',') +1 loop
sort_tab(regexp_substr(p_list,'[^,]+', 1, i)) := i;
end loop;
for i in indices of sort_tab loop
sorted_list := sorted_list || ', ' || i;
end loop;
return ltrim(sorted_list,', ');
end sort_number_list;
I've used the indices of syntax added in 21c which makes looping around an associative array less verbose. Alternatively you can use the first and next collection methods.
create or replace TYPE t_numlist IS table OF varchar2(32700);
create or replace function sort_str_of_num(p_string in varchar2,p_delimiter in char) return
varchar2 as
v_strnum varchar2(32767):=p_string;
v_delimiter char(1):=p_delimiter;
l_num t_numlist:=t_numlist();
incr integer :=1;
b_bool boolean:= true;
begin
v_strnum := trim(replace(v_strnum,v_delimiter,' '));
loop
l_num.extend;
l_num(incr):= regexp_substr(v_strnum,'[0-9]{1,}');
v_strnum := trim(SUBSTR(v_strnum,instr(v_strnum,' ')+1));
if (instr(v_strnum,' ')+1)=1 then
l_num.extend;
l_num(incr+1):=regexp_substr(v_strnum,'[0-9]{1,}');
exit;
end if;
incr:=incr+1;
end loop;
for i in (select column_value from table(l_num) order by to_number(column_value))loop
if b_bool then v_strnum:=i.column_value;
else v_strnum:=i.column_value||', '||v_strnum;
end if;
b_bool:=false;
end loop;
return v_strnum;
exception
when others then dbms_output.put_line('wrong entry');
end;
This will sort any list of numbers in a string and numbers can have any digit count. Also u can enter any character to be the delimiter between numbers (delimiter must be 1 character) and space character in number list string is ignored.
One way would be to use the ability of xmltable to parse a comma-separated list into XML elements, then use its getstringval() method to convert each element into a string, then finally re-aggregate the strings using listagg:
create or replace function sort_number_list
( p_list varchar2 )
return varchar2
as
sorted_list varchar2(4000);
begin
select listagg(x.column_value.getstringval(), ', ') within group (order by to_number(x.column_value.getstringval()))
into sorted_list
from xmltable(p_list) x;
return sorted_list;
end sort_number_list;
Test:
select sort_number_list('97,3,-123,4')
from dual;
SORT_NUMBER_LIST('97,3,-123,4')
------------------------------------------------------------
-123, 3, 4, 97

split a string into individual elements in PL/SQL

This is for PL/SQL
I am being passed in a series of strings, delimited by a comma, in a string.
I would like to take those delimited strings and create an IN clause within my query.
I was thinking a simply add an open parenthesis and single quote to the front and, at the end of the string and single quote with a closing parenthesis, trim the spaces, and swap the commas out with a quote comma quote string. the problem falls when the string has an embedded space in the delimited string.
For example, the string that is being passed in is
TNR, abc, D N, w
What i am looking to receive after processing it would be
('TNR','ABC','D N','W')
I'm not sure where i should begin.
Use REPLACE to change , into ', ' and then prepend (' and append '):
DECLARE
list VARCHAR2(50) := 'TNR, abc, D N, w';
BEGIN
list := '(''' || REPLACE(list, ', ', ''', ''') || ''')';
DBMS_OUTPUT.PUT_LINE(list);
END;
/
If the white space after the comma is optional then you can use REGEXP_REPLACE rather than REPLACE:
DECLARE
list VARCHAR2(50) := 'TNR,abc, D N, w';
BEGIN
list := '(''' || REGEXP_REPLACE(list, ',\s*', ''', ''') || ''')';
DBMS_OUTPUT.PUT_LINE(list);
END;
/
Which both output:
('TNR', 'abc', 'D N', 'w')
db<>fiddle here
What you described is way too complicated. Just split the string into rows and use them in an IN clause as a subquery.
This is example that runs in SQL*Plus (hence single quotes and && for the substitution variable); query returns one row (from the dual table) as its dummy column contains letter X which is part of the input string:
SQL> SELECT *
2 FROM DUAL
3 WHERE dummy IN ( SELECT TRIM (REGEXP_SUBSTR ('&&par_string',
4 '[^,]+',
5 1,
6 LEVEL))
7 FROM DUAL
8 CONNECT BY LEVEL <= REGEXP_COUNT ('&&par_string', ',') + 1);
Enter value for par_string: TNR, abc, D N,X, w
D
-
X
SQL>
Subquery itself returns
SQL> SELECT TRIM (REGEXP_SUBSTR ('&&par_string',
2 '[^,]+',
3 1,
4 LEVEL))
5 FROM DUAL
6 CONNECT BY LEVEL <= REGEXP_COUNT ('&&par_string', ',') + 1;
Enter value for par_string: TNR, abc, D N,X, w
TRIM(REGEXP_SUBSTR
------------------
TNR
abc
D N
X
w
SQL>
If you use bind variable, you'd use e.g. :par_string instead of '&&par_string'; or, if it was passed to a procedure or a function, you'd just just the parameter name - par_string.

Oracle Invalid Number error while passing comma separated numbers

Here is he problem desc.
I am trying to pass comma separated integer values(stored in a Varchar variable) to a IN conditon.
local_var VARCHAR2(20);
local_var := 1,2;
WHERE someid IN (local_var) --Just pasted the where clause where IN condition is being used
someid is of type NUMBER,
I get an invalid number error while executing this statement in a cursor. I am assuming this problem is because of someid being a NUMBER,
What is the possible solution? I cannot pass the values say 1,2 directly to IN clause, because I need to build the same based on some conditions,
I have tried passing comma separated values in quotes too, didnt seem to work
Using Oracle version 11.2
I have tried using
local_var := '1,2';
local_var := '''1','2'''; etc..
Nothing seemed to work
Urgent help required! Thanks in advance
You are getting that error, obviously, because someid is of number datatype and you are passing a comma separated string, which is considered as one value of string datatype and not a list of values as you would expect, to the IN clause. But you can transform that string, using regexp_substr and regexp_count regular expression functions, into table rows an then use them in the IN clause. Here is an example:
SQL> declare
2 cursor your_cursor(c_str_value varchar2) is
3 with str_values as(
4 select regexp_substr(c_str_value, '[^,]+', 1, level) as value
5 from dual
6 connect by level <= regexp_count(c_str_value, '[^,]+')
7 )
8 select first_name -- here goes your query
9 from employees e
10 where e.department_id in (select to_number(value)
11 from str_values);
12
13 l_local_var varchar2(20);
14 begin
15 l_local_var := '100,102,103'; -- your comma separated string
16 for i in your_cursor(l_local_var) -- for the sake of demonstration
17 loop
18 dbms_output.put_line(i.first_name);
19 end loop;
20 end;
21 /
Nancy
Daniel
John
Ismael
Jose Manuel
Luis
PL/SQL procedure successfully completed
You can not pass a list as arguments like that.
You could go with using a Dynamic PL/SQL block. Something like:
DECLARE
local_var VARCHAR2(20) := '1,2';
req VARCHAR(1000);
BEGIN
req := 'BEGIN
FOR c IN (SELECT someid FROM my_table WHERE someid IN (' || local_var || '))
LOOP
DBMS_OUTPUT.PUT_LINE('someid ' || c.someid);
END LOOP;
END;';
EXECUTE IMMEDIATE req;
END;
But… Be very careful of PL/SQL injections.
your variable type is VARCHAR2 and you are assigning number types, t'y u r getting that error. please use single quotations.
for example #
local_var VARCHAR2(20);
local_var := '1';
WHERE someid IN (local_var);
this is the way
create table a(local_var varchar2(10))
insert into a values('2');
select * from a;
declare
y varchar2(10);
cursor c1(v_val varchar2) is select * from a;
Begin
y := '1,2';
for i in c1(y)
loop
dbms_output.put_line(i.local_var);
end loop ;
end;
/

Multiple REPLACE function in Oracle

I am using the REPLACE function in oracle to replace values in my string like;
SELECT REPLACE('THE NEW VALUE IS #VAL1#','#VAL1#','55') from dual
So this is OK to replace one value, but what about 20+, should I use 20+ REPLACE function or is there a more practical solution.
All ideas are welcome.
Even if this thread is old is the first on Google, so I'll post an Oracle equivalent to the function implemented here, using regular expressions.
Is fairly faster than nested replace(), and much cleaner.
To replace strings 'a','b','c' with 'd' in a string column from a given table
select regexp_replace(string_col,'a|b|c','d') from given_table
It is nothing else than a regular expression for several static patterns with 'or' operator.
Beware of regexp special characters!
The accepted answer to how to replace multiple strings together in Oracle suggests using nested REPLACE statements, and I don't think there is a better way.
If you are going to make heavy use of this, you could consider writing your own function:
CREATE TYPE t_text IS TABLE OF VARCHAR2(256);
CREATE FUNCTION multiple_replace(
in_text IN VARCHAR2, in_old IN t_text, in_new IN t_text
)
RETURN VARCHAR2
AS
v_result VARCHAR2(32767);
BEGIN
IF( in_old.COUNT <> in_new.COUNT ) THEN
RETURN in_text;
END IF;
v_result := in_text;
FOR i IN 1 .. in_old.COUNT LOOP
v_result := REPLACE( v_result, in_old(i), in_new(i) );
END LOOP;
RETURN v_result;
END;
and then use it like this:
SELECT multiple_replace( 'This is #VAL1# with some #VAL2# to #VAL3#',
NEW t_text( '#VAL1#', '#VAL2#', '#VAL3#' ),
NEW t_text( 'text', 'tokens', 'replace' )
)
FROM dual
This is text with some tokens to replace
If all of your tokens have the same format ('#VAL' || i || '#'), you could omit parameter in_old and use your loop-counter instead.
Bear in mind the consequences
SELECT REPLACE(REPLACE('TEST123','123','456'),'45','89') FROM DUAL;
will replace the 123 with 456, then find that it can replace the 45 with 89.
For a function that had an equivalent result, it would have to duplicate the precedence (ie replacing the strings in the same order).
Similarly, taking a string 'ABCDEF', and instructing it to replace 'ABC' with '123' and 'CDE' with 'xyz' would still have to account for a precedence to determine whether it went to '123EF' or ABxyzF'.
In short, it would be difficult to come up with anything generic that would be simpler than a nested REPLACE (though something that was more of a sprintf style function would be a useful addition).
In case all your source and replacement strings are just one character long, you can simply use the TRANSLATE function:
SELECT translate('THIS IS UPPERCASE', 'THISUP', 'thisup')
FROM DUAL
See the Oracle documentation for details.
This is an old post, but I ended up using Peter Lang's thoughts, and did a similar, but yet different approach. Here is what I did:
CREATE OR REPLACE FUNCTION multi_replace(
pString IN VARCHAR2
,pReplacePattern IN VARCHAR2
) RETURN VARCHAR2 IS
iCount INTEGER;
vResult VARCHAR2(1000);
vRule VARCHAR2(100);
vOldStr VARCHAR2(50);
vNewStr VARCHAR2(50);
BEGIN
iCount := 0;
vResult := pString;
LOOP
iCount := iCount + 1;
-- Step # 1: Pick out the replacement rules
vRule := REGEXP_SUBSTR(pReplacePattern, '[^/]+', 1, iCount);
-- Step # 2: Pick out the old and new string from the rule
vOldStr := REGEXP_SUBSTR(vRule, '[^=]+', 1, 1);
vNewStr := REGEXP_SUBSTR(vRule, '[^=]+', 1, 2);
-- Step # 3: Do the replacement
vResult := REPLACE(vResult, vOldStr, vNewStr);
EXIT WHEN vRule IS NULL;
END LOOP;
RETURN vResult;
END multi_replace;
Then I can use it like this:
SELECT multi_replace(
'This is a test string with a #, a $ character, and finally a & character'
,'#=%23/$=%24/&=%25'
)
FROM dual
This makes it so that I can can any character/string with any character/string.
I wrote a post about this on my blog.
I have created a general multi replace string Oracle function by a table of varchar2 as parameter.
The varchar will be replaced for the position rownum value of table.
For example:
Text: Hello {0}, this is a {2} for {1}
Parameters: TABLE('world','all','message')
Returns:
Hello world, this is a message for all.
You must create a type:
CREATE OR REPLACE TYPE "TBL_VARCHAR2" IS TABLE OF VARCHAR2(250);
The funcion is:
CREATE OR REPLACE FUNCTION FN_REPLACETEXT(
pText IN VARCHAR2,
pPar IN TBL_VARCHAR2
) RETURN VARCHAR2
IS
vText VARCHAR2(32767);
vPos INT;
vValue VARCHAR2(250);
CURSOR cuParameter(POS INT) IS
SELECT VAL
FROM
(
SELECT VAL, ROWNUM AS RN
FROM (
SELECT COLUMN_VALUE VAL
FROM TABLE(pPar)
)
)
WHERE RN=POS+1;
BEGIN
vText := pText;
FOR i IN 1..REGEXP_COUNT(pText, '[{][0-9]+[}]') LOOP
vPos := TO_NUMBER(SUBSTR(REGEXP_SUBSTR(pText, '[{][0-9]+[}]',1,i),2, LENGTH(REGEXP_SUBSTR(pText, '[{][0-9]+[}]',1,i)) - 2));
OPEN cuParameter(vPos);
FETCH cuParameter INTO vValue;
IF cuParameter%FOUND THEN
vText := REPLACE(vText, REGEXP_SUBSTR(pText, '[{][0-9]+[}]',1,i), vValue);
END IF;
CLOSE cuParameter;
END LOOP;
RETURN vText;
EXCEPTION
WHEN OTHERS
THEN
RETURN pText;
END FN_REPLACETEXT;
/
Usage:
TEXT_RETURNED := FN_REPLACETEXT('Hello {0}, this is a {2} for {1}', TBL_VARCHAR2('world','all','message'));
Thanks for the answer. Rather than specifying the translation in the call, you can also do this using a cursor as shown below.
create or replace function character_substitutions (input_str varchar2)
return varchar2
as
v_result VARCHAR2(4000);
cursor c_translate_table is
select '&' as symbol_to_replace, 'amp' as symbol_in_return_string from dual
union all
select '/' as symbol_to_replace, '_' as symbol_in_return_string from dual
union all
select '"' as symbol_to_replace, 'in' as symbol_in_return_string from dual
union all
select '%' as symbol_to_replace, 'per' as symbol_in_return_string from dual
union all
select '.' as symbol_to_replace, '_' as symbol_in_return_string from dual;
begin
v_result := input_str;
for r_translate in c_translate_table loop
v_result := REPLACE( v_result, r_translate.symbol_to_replace, r_translate.symbol_in_return_string);
end loop;
return v_result;
end;
/
Another example of how to apply an ordered list of character replacements. In this case:
convert a backslash to a slash
convert a double slash occurrence to a single slash
convert a single quote to an escaped single quote
For example, convert from:
\\server\Folder\611891\Joe's Doc.pdf
to
/server/Folder/611891/Joe\'s Doc.pdf
Can use a recursive CTE to iterate through the ordered list in sequence. First part demonstrates how the string evolves:
WITH subs AS (
SELECT '\' convert_from, '/' convert_to, 1 seq FROM DUAL
UNION SELECT '//', '/', 2 FROM DUAL
UNION SELECT '''', '\''', 3 FROM DUAL
),
rcte(convert_from, convert_to, seq, src) AS (
SELECT '', '', 0, '\\server\Folder\611891\Joe''s Doc.pdf' src FROM DUAL
UNION ALL
SELECT s.convert_from, s.convert_to, s.seq, REPLACE(r.src, s.convert_from, s.convert_to)
FROM rcte r
JOIN subs s ON (s.seq = r.seq + 1)
)
SELECT *
FROM rcte
ORDER BY seq
;
| CONVERT_FROM | CONVERT_TO | SEQ | SRC |
|--------------|------------|-----|--------------------------------------|
| (null) | (null) | 0 | \\server\Folder\611891\Joe's Doc.pdf |
| \ | / | 1 | //server/Folder/611891/Joe's Doc.pdf |
| // | / | 2 | /server/Folder/611891/Joe's Doc.pdf |
| ' | \' | 3 | /server/Folder/611891/Joe\'s Doc.pdf |
But actually only interested in the final result:
SELECT src
FROM rcte
WHERE seq = (SELECT MAX(seq) FROM subs)
;
| SRC |
|--------------------------------------|
| /server/Folder/611891/Joe\'s Doc.pdf |

Resources