I have 2 strings str1: 'abc,def,ghi' and str2: 'tyu,abc,fgh'.
I want to compare these two strings using the delimiter ,. Now since the 2 strings have abc it should return true. I want a function in Oracle SQL which can perform this operation.
Looks complicated but it is just a couple of helper functions to split a list into separate values (to be contained into a table type) and then a very simple function to test the intersection of two collections.
Oracle Setup:
CREATE TYPE VARCHAR2s_Table AS TABLE OF VARCHAR2(4000);
CREATE FUNCTION regexp_escape(
expression VARCHAR2
) RETURN VARCHAR2 DETERMINISTIC
AS
BEGIN
RETURN REGEXP_REPLACE( expression, '([$^[()+*?{\|])', '\\\1', 1, 0, 'c' );
END;
/
CREATE FUNCTION splitList(
list VARCHAR2,
delim VARCHAR2 := ','
) RETURN VARCHAR2s_Table DETERMINISTIC
AS
pattern VARCHAR2(256);
len BINARY_INTEGER;
t_items VARCHAR2s_Table := VARCHAR2s_Table();
BEGIN
IF list IS NULL THEN
NULL;
ELSIF delim IS NULL THEN
t_items.EXTEND( LENGTH( list ) );
FOR i IN 1 .. LENGTH( list ) LOOP
t_items(i) := SUBSTR( list, i, 1 );
END LOOP;
ELSE
pattern := '(.*?)($|' || REGEXP_ESCAPE( delim ) || ')';
len := REGEXP_COUNT( list, pattern ) - 1;
t_items.EXTEND( len );
IF len = 1 THEN
t_items(1) := list;
ELSE
FOR i IN 1 .. len LOOP
t_items(i) := REGEXP_SUBSTR( list, pattern, 1, i, NULL, 1 );
END LOOP;
END IF;
END IF;
RETURN t_items;
END;
/
CREATE FUNCTION check_list_intersect(
list1 VARCHAR2,
list2 VARCHAR2
) RETURN NUMBER DETERMINISTIC
AS
BEGIN
IF splitList( list1 ) MULTISET INTERSECT splitList( list2 ) IS EMPTY THEN
RETURN 0;
ELSE
RETURN 1;
END IF;
END;
/
Query 1:
SELECT check_list_intersect( 'abc,def,ghi', 'abc' ) AS matches
FROM DUAL;
Results:
MATCHES
---------
1
Query 2:
SELECT check_list_intersect( 'abc,def,ghi', 'abcd' ) AS matches
FROM DUAL;
Results:
MATCHES
---------
0
The below will make the trick.
with temp as (
select 1 strid, 'abc,def,ghi' Error from dual
union all
select 2, 'tyu,abc,fgh' from dual
)
select str
from (
SELECT strid, trim(regexp_substr(str, '[^,]+', 1, level)) str
FROM (SELECT strid, Error str FROM temp) t
CONNECT BY instr(str, ',', 1, level - 1) > 0
)
group by str
having count(distinct strid) > 1;
Warning: This answer is not fully correct. (See the comments.)
Starting from dcieslak answer(csv split with regexp), a variation of the subject would be:
create or replace function check_string_intersec(str1 varchar2, str2 varchar2) return number
is
begin
for k in (SELECT trim(regexp_substr(str1, '[^,]+', 1, level)) item
FROM dual
CONNECT BY instr(str1, ',', 1, level - 1) > 0
)
loop
if instr(str2, k.item,1) > 0 then return 1; end if;
end loop;
return 0;
end;
This splits first string and search for every item in the second string.
Related
function that take two parameters, the first to be a string and the second is the order (Asc or Desc) and the returned output to be ordering the first string as per the second parameter.
IN : dgtak
OUT: adgkt
Tried this but doesn't seem to work
CREATE OR REPLACE FUNCTION order_string(my_string IN VARCHAR2)
RETURN VARCHAR2 IS
ret_string VARCHAR2(4000);
BEGIN
SELECT LISTAGG(regexp_substr(my_string, '\w', 1, level), '') WITHIN
GROUP(
ORDER BY 1)
INTO ret_string
FROM dual
CONNECT BY regexp_substr(my_string, '\w', 1, level) IS NOT NULL;
RETURN ret_string;
END;
select order_string('dgtak') as RESULT from dual;
Here's one option:
SQL> create or replace function order_string (par_string in varchar2, par_order in varchar2)
2 return varchar2
3 is
4 retval varchar2(100);
5 begin
6 with temp (val) as
7 -- split PAR_STRING to rows
8 (select substr(par_string, level, 1)
9 from dual
10 connect by level <= length(par_string)
11 )
12 -- aggregate characters back in ascending or descending order
13 select case when par_order = 'Asc' then listagg(val, '') within group (order by val asc)
14 when par_order = 'Desc' then listagg(val, '') within group (order by val desc)
15 else null
16 end
17 into retval
18 from temp;
19
20 return retval;
21 end;
22 /
Function created.
Testing:
SQL> select order_string('dfag', 'Asc') result_asc,
2 order_string('dfag', 'Desc') result_desc
3 from dual;
RESULT_ASC RESULT_DESC
-------------------- --------------------
adfg gfda
SQL>
Just for fun, here's a procedural version. It has more lines of code than the SQL version but in my tests it's slightly faster.
create or replace function order_string
( p_string varchar2
, p_reverse varchar2 default 'N' )
return varchar2
as
pragma udf;
type letter_tt is table of number index by varchar2(1);
letters letter_tt := letter_tt();
letter varchar2(1);
sorted_string long;
string_length integer := length(p_string);
begin
-- Store all characters of p_string as indices of array:
for i in 1..string_length loop
letter := substr(p_string,i,1);
if letters.exists(letter) then
letters(letter) := letters(letter) +1;
else
letters(letter) := 1;
end if;
end loop;
-- Loop through array appending each array index to sorted_string
for i in indices of letters loop
for r in 1..letters(i) loop
sorted_string := sorted_string || i;
end loop;
end loop;
if p_reverse = 'Y' then
select reverse(sorted_string) into sorted_string from dual;
end if;
return sorted_string;
end order_string;
I've used the 21c indices of loop iterator, but you can write a conventional loop in earlier versions. You might also use two alternative loops for ascending and descending order in place of my hack.
How can I count number of occurrences of the character - in a varchar2 string?
Example:
select XXX('123-345-566', '-') from dual;
----------------------------------------
2
Here you go:
select length('123-345-566') - length(replace('123-345-566','-',null))
from dual;
Technically, if the string you want to check contains only the character you want to count, the above query will return NULL; the following query will give the correct answer in all cases:
select coalesce(length('123-345-566') - length(replace('123-345-566','-',null)), length('123-345-566'), 0)
from dual;
The final 0 in coalesce catches the case where you're counting in an empty string (i.e. NULL, because length(NULL) = NULL in ORACLE).
REGEXP_COUNT should do the trick:
select REGEXP_COUNT('123-345-566', '-') from dual;
Here's an idea: try replacing everything that is not a dash char with empty string. Then count how many dashes remained.
select length(regexp_replace('123-345-566', '[^-]', '')) from dual
I justed faced very similar problem... BUT RegExp_Count couldn't resolved it.
How many times string '16,124,3,3,1,0,' contains ',3,'? As we see 2 times, but RegExp_Count returns just 1. Same thing is with ''bbaaaacc' and when looking in it 'aa' - should be 3 times and RegExp_Count returns just 2.
select REGEXP_COUNT('336,14,3,3,11,0,' , ',3,') from dual;
select REGEXP_COUNT('bbaaaacc' , 'aa') from dual;
I lost some time to research solution on web. Couldn't' find... so i wrote my own function that returns TRUE number of occurance. Hope it will be usefull.
CREATE OR REPLACE FUNCTION EXPRESSION_COUNT( pEXPRESSION VARCHAR2, pPHRASE VARCHAR2 ) RETURN NUMBER AS
vRET NUMBER := 0;
vPHRASE_LENGTH NUMBER := 0;
vCOUNTER NUMBER := 0;
vEXPRESSION VARCHAR2(4000);
vTEMP VARCHAR2(4000);
BEGIN
vEXPRESSION := pEXPRESSION;
vPHRASE_LENGTH := LENGTH( pPHRASE );
LOOP
vCOUNTER := vCOUNTER + 1;
vTEMP := SUBSTR( vEXPRESSION, 1, vPHRASE_LENGTH);
IF (vTEMP = pPHRASE) THEN
vRET := vRET + 1;
END IF;
vEXPRESSION := SUBSTR( vEXPRESSION, 2, LENGTH( vEXPRESSION ) - 1);
EXIT WHEN ( LENGTH( vEXPRESSION ) = 0 ) OR (vEXPRESSION IS NULL);
END LOOP;
RETURN vRET;
END;
I thought of
SELECT LENGTH('123-345-566') - LENGTH(REPLACE('123-345-566', '-', '')) FROM DUAL;
You can try this
select count( distinct pos) from
(select instr('123-456-789', '-', level) as pos from dual
connect by level <=length('123-456-789'))
where nvl(pos, 0) !=0
it counts "properly" olso for how many 'aa' in 'bbaaaacc'
select count( distinct pos) from
(select instr('bbaaaacc', 'aa', level) as pos from dual
connect by level <=length('bbaaaacc'))
where nvl(pos, 0) !=0
here is a solution that will function for both characters and substrings:
select (length('a') - nvl(length(replace('a','b')),0)) / length('b')
from dual
where a is the string in which you search the occurrence of b
have a nice day!
SELECT {FN LENGTH('123-345-566')} - {FN LENGTH({FN REPLACE('123-345-566', '#', '')})} FROM DUAL
select count(*)
from (
select substr('K_u_n_a_l',level,1) str
from dual
connect by level <=length('K_u_n_a_l')
)
where str ='_';
I want to use my result of function e.g. 'S500,S600,S700,S800' in a subquery in another script like:
where dept_no in (my result of function)
So I want to convert my string result to be like this ('S500','S600','S700','S800').
I tried to do this with dynamic SQL but I can't get it to work.
Hope below snipet suffice your requirement.
Approach 1 -> More effective
--Create a table type of VARCHAR
CREATE OR REPLACE type string_table
IS
TABLE OF VARCHAR2(100);
--Function to return tabl type
CREATE OR REPLACE
FUNCTION string_manipulate
RETURN string_table
AS
str_tab string_table;
BEGIN
SELECT 's00'||level bulk collect INTO str_tab FROM dual CONNECT BY level < 10;
RETURN str_tab;
end;
--Use function in the query
SELECT distinct 1
FROM
(SELECT 's001' dn FROM dual
UNION ALL
SELECT 's002' dn FROM dual
UNION ALL
SELECT 's003' dn FROM dual
UNION ALL
SELECT 's004' dn FROM dual
UNION ALL
SELECT 's005' dn FROM dual
UNION ALL
SELECT 's006' dn FROM dual
UNION ALL
SELECT 's007' dn FROM dual
UNION ALL
SELECT 's008' dn FROM dual
UNION ALL
SELECT 's009' dn FROM dual
)a
WHERE a.dn IN
(SELECT * FROM TABLE(string_manipulate)
);
--Approach 2
--Function to get output as mentioned.
CREATE OR REPLACE
FUNCTION string_manipulate
RETURN VARCHAR2
AS
BEGIN
RETURN 'S2009,S2020,S2021';
END;
-- Use function value in a query
SELECT 1
FROM dual
WHERE '''S2009'',''S2020'',''S2021''' = (''''
||REPLACE(string_manipulate,',',''',''')
||'''');
You need an iterator and text splitting by comma sign:
select empno,ename,sal,deptno
from emp
where empno in (
select to_number(
rtrim(
substr(emps,
instr(emps,',',1,iter.pos)+1,
instr(emps,',',1,iter.pos+1) -
instr(emps,',',1,iter.pos)),',')) emps
from (select ','||'7654,7698,7782,7788'||',' emps from t1) csv,
(select rownum pos from emp) iter
where iter.pos <= ((length(csv.emps) -
length(replace(csv.emps,',')))/length(','))-1
)
But better rewrite your function to return cursor.
you can use collection:
SELECT *
FROM YOUR_TABLE
WHERE DEPT_NO IN (SELECT *
FROM TABLE (SPLIT ('S500,S600,S700,S800')))--splits text with comma, for other chars use split(text, split_char)
With usage of MEMBER OF
SELECT *
FROM YOUR_TABLE
WHERE DEPT_NO MEMBER OF SPLIT ('S500,S600,S700,S800')--splits text with comma, for other chars use split(text, split_char)
the split fuction is:
CREATE OR REPLACE TYPE SPLIT_TBL AS TABLE OF VARCHAR2 (32767);
CREATE OR REPLACE FUNCTION SPLIT (P_LIST VARCHAR2, P_DEL VARCHAR2 := ',')
RETURN SPLIT_TBL
PIPELINED
IS
L_IDX PLS_INTEGER;
L_LIST VARCHAR2 (32767) := P_LIST;
BEGIN
LOOP
L_IDX := INSTR (L_LIST, P_DEL);
IF L_IDX > 0
THEN
PIPE ROW (SUBSTR (L_LIST, 1, L_IDX - 1));
L_LIST := SUBSTR (L_LIST, L_IDX + LENGTH (P_DEL));
ELSE
PIPE ROW (L_LIST);
EXIT;
END IF;
END LOOP;
RETURN;
END SPLIT;
FUNCTION GET_TS_EACH_DAY_DEPARTMENT (P_SER_NO VARCHAR2,
P_TS_DATE DATE
)
RETURN STRING_TABLE
IS
V_DEPT_NO VARCHAR2 (4000);
V_DEPT VARCHAR2(4000);
V_TABLE STRING_TABLE:=STRING_TABLE();
J NUMBER:=1;
BEGIN
for i in (select distinct ts_day dayy from WEB_TS_USER_LOCATIONS_V ) loop
V_TABLE.EXTEND;
V_TABLE(J):= WEB_TS_PKG.GET_TS_DAY_DEPARTMENT (P_SER_NO ,P_TS_DATE , i.dayy );
J:=J+1;
end loop;
RETURN V_TABLE;
END GET_TS_EACH_DAY_DEPARTMENT;
I have string 'ABC' I need to split into rows as below
A
B
C
.I know how do do when delimiter is present. How about when delimiter is not present
with test as
(select 'A,B,C' col1 from dual)
select regexp_substr(col1, '[^,]+', 1, rownum) result1
from test
connect by level <= length(regexp_replace(col1, '[^,]+')) + 1;
Without a delimiter it should be even easier - use the same approach, but just use substr with level as the index of the string:
with test as
(select 'ABC' col1 from dual)
select substr(col1, level, 1) result1
from test
connect by level <= length(col1);
You can use a function like this
-- define type
CREATE OR REPLACE TYPE TABLE_OF_STRING AS TABLE OF VARCHAR2(32767);
-- function
function SPLIT_STRING_TO_STRINGS
(
p_list varchar2,
p_delimiter varchar2 := ','
) return TABLE_OF_STRING pipelined
is
l_idx pls_integer;
l_list varchar2(32767) := p_list;
begin
loop
l_idx := instr(l_list, p_delimiter);
if l_idx > 0 then
pipe row(substr(l_list, 1, l_idx-1));
l_list := substr(l_list, l_idx + length(p_delimiter));
else
pipe row(l_list);
exit;
end if;
end loop;
return;
end SPLIT_STRING_TO_STRINGS;
-- usage example
select * from table(SPLIT_STRING_TO_STRINGS('A,B,C',','))
How can I count number of occurrences of the character - in a varchar2 string?
Example:
select XXX('123-345-566', '-') from dual;
----------------------------------------
2
Here you go:
select length('123-345-566') - length(replace('123-345-566','-',null))
from dual;
Technically, if the string you want to check contains only the character you want to count, the above query will return NULL; the following query will give the correct answer in all cases:
select coalesce(length('123-345-566') - length(replace('123-345-566','-',null)), length('123-345-566'), 0)
from dual;
The final 0 in coalesce catches the case where you're counting in an empty string (i.e. NULL, because length(NULL) = NULL in ORACLE).
REGEXP_COUNT should do the trick:
select REGEXP_COUNT('123-345-566', '-') from dual;
Here's an idea: try replacing everything that is not a dash char with empty string. Then count how many dashes remained.
select length(regexp_replace('123-345-566', '[^-]', '')) from dual
I justed faced very similar problem... BUT RegExp_Count couldn't resolved it.
How many times string '16,124,3,3,1,0,' contains ',3,'? As we see 2 times, but RegExp_Count returns just 1. Same thing is with ''bbaaaacc' and when looking in it 'aa' - should be 3 times and RegExp_Count returns just 2.
select REGEXP_COUNT('336,14,3,3,11,0,' , ',3,') from dual;
select REGEXP_COUNT('bbaaaacc' , 'aa') from dual;
I lost some time to research solution on web. Couldn't' find... so i wrote my own function that returns TRUE number of occurance. Hope it will be usefull.
CREATE OR REPLACE FUNCTION EXPRESSION_COUNT( pEXPRESSION VARCHAR2, pPHRASE VARCHAR2 ) RETURN NUMBER AS
vRET NUMBER := 0;
vPHRASE_LENGTH NUMBER := 0;
vCOUNTER NUMBER := 0;
vEXPRESSION VARCHAR2(4000);
vTEMP VARCHAR2(4000);
BEGIN
vEXPRESSION := pEXPRESSION;
vPHRASE_LENGTH := LENGTH( pPHRASE );
LOOP
vCOUNTER := vCOUNTER + 1;
vTEMP := SUBSTR( vEXPRESSION, 1, vPHRASE_LENGTH);
IF (vTEMP = pPHRASE) THEN
vRET := vRET + 1;
END IF;
vEXPRESSION := SUBSTR( vEXPRESSION, 2, LENGTH( vEXPRESSION ) - 1);
EXIT WHEN ( LENGTH( vEXPRESSION ) = 0 ) OR (vEXPRESSION IS NULL);
END LOOP;
RETURN vRET;
END;
I thought of
SELECT LENGTH('123-345-566') - LENGTH(REPLACE('123-345-566', '-', '')) FROM DUAL;
You can try this
select count( distinct pos) from
(select instr('123-456-789', '-', level) as pos from dual
connect by level <=length('123-456-789'))
where nvl(pos, 0) !=0
it counts "properly" olso for how many 'aa' in 'bbaaaacc'
select count( distinct pos) from
(select instr('bbaaaacc', 'aa', level) as pos from dual
connect by level <=length('bbaaaacc'))
where nvl(pos, 0) !=0
here is a solution that will function for both characters and substrings:
select (length('a') - nvl(length(replace('a','b')),0)) / length('b')
from dual
where a is the string in which you search the occurrence of b
have a nice day!
SELECT {FN LENGTH('123-345-566')} - {FN LENGTH({FN REPLACE('123-345-566', '#', '')})} FROM DUAL
select count(*)
from (
select substr('K_u_n_a_l',level,1) str
from dual
connect by level <=length('K_u_n_a_l')
)
where str ='_';