split string in oracle query - oracle

I am trying to fetch phone numbers from my Oracle database table. The phone numbers may be separated with comma or "/". Now I need to split those entries which have a "/" or comma and fetch the first part.

Follow this approach,
with t as (
select 'Test 1' name from dual
union
select 'Test 2, extra 3' from dual
union
select 'Test 3/ extra 3' from dual
union
select ',extra 4' from dual
)
select
name,
regexp_instr(name, '[/,]') pos,
case
when regexp_instr(name, '[/,]') = 0 then name
else substr(name, 1, regexp_instr(name, '[/,]')-1)
end first_part
from
t
order by first_part
;

Lookup substr and instr functions or solve the puzzle using regexp.

I added a table test with one column phone_num. And added rows similar to your description.
select *
from test;
PHONE_NUM
------------------------------
0123456789
0123456789/1234
0123456789,1234
3 rows selected.
select
case
when instr(phone_num, '/') > 0 then substr(phone_num, 0, instr(phone_num, '/')-1)
when instr(phone_num, ',') > 0 then substr(phone_num, 0, instr(phone_num, ',')-1)
else phone_num
end phone_num
from test
PHONE_NUM
------------------------------
0123456789
0123456789
0123456789
3 rows selected.
This generally works. Although it will fail if you have rows with commas and slashes.

Related

Regular Expression with Connect by

I have this query:
select regexp_substr('1,2,3,4,5','[^,]+',1,level)
from test1
connect by instr('1,2,3,4,5',',',1,level-1)>0;
Please help me to understand this query especially the use of a level and connect by and level-1.
LEVEL and CONNECT BY are used to generate sequences, see this simple example:
select level
from dual
connect by level < 10;
LEVEL
=======
1
2
3
4
5
6
7
8
9
instr('1,2,3,4,5', ',' , 1, level-1) > 0 counts the number of elements separated by comma maybe this version is easier to understand, it does the same:
select regexp_substr('1,2,3,4,5','[^,]+',1,level)
from dual
connect by LEVEL <= REGEXP_COUNT('1,2,3,4,5', ',')+1;
LEVEL <= REGEXP_COUNT('1,2,3,4,5', ',')+1 or instr('1,2,3,4,5', ',' , 1, level-1) > 0 means "run five times".
So, your select is basically a shortcut for
select regexp_substr('1,2,3,4,5','[^,]+', 1, 1) from dual union all
select regexp_substr('1,2,3,4,5','[^,]+', 1, 2) from dual union all
select regexp_substr('1,2,3,4,5','[^,]+', 1, 3) from dual union all
select regexp_substr('1,2,3,4,5','[^,]+', 1, 4) from dual union all
select regexp_substr('1,2,3,4,5','[^,]+', 1, 5) from dual;
#Wernfried's answer is excellent, but you should know there is a big risk with the regex of the format '[^,]+' which one sees often as an example of how to parse delimited strings.
This only works when all elements of the list are present. For an eye-opener, try it with the 2nd element as NULL:
select regexp_substr('1,,3,4,5', '[^,]+', 1, 3) from dual;
REGEXP_SUBSTR('1,,3,4,5','[^,]+',1,3)
-------------------------------------
4
1 row selected.
WHAT? '4' is most definitely NOT the 3rd element of that list! As you can see the NULL is not handled.
Please use this format of REGEXP_SUBSTR which does handle NULL list elements:
select regexp_substr('1,,3,4,5','(.*?)(,|$)', 1, 3, NULL, 1) from dual;
REGEXP_SUBSTR('1,,3,4,5','(.*?)(,|$)',1,3,NULL,1)
-------------------------------------------------
3
1 row selected.
The regex defines defines 2 groups, an optional set of any characters followed by a comma or the end of the line. The arguments to REGEXP_SUBSTR
say to return the 1st group of the 3rd instance of this match.

How to define default escape character in Oracle?

I have table
id name
__________
1 name1
2 name2
3 _name3
and I want to select all names, that starting with '_' character.
SELECT name FROM table1 WHERE name like '_%'
But this query returns all rows from table. Maybe anyone knows some solution for this problem (without using ESCAPE keyword)? Or, is there opportunity to set default escape character in Oracle?
Apparently, you cannot:
char1 [ NOT ] { LIKE | LIKEC | LIKE2 | LIKE4 }
char2 [ ESCAPE esc_char ]
[...]
If esc_char is not specified, then there is no default escape
character.
I want to select all names, that starting with '_' character.
Use SUBSTR.
SQL> WITH DATA AS(
2 SELECT 1 ID, 'name1' NAME FROM dual UNION ALL
3 SELECT 2, 'name2' FROM dual UNION ALL
4 SELECT 3 , '_name3' FROM dual
5 )
6 SELECT * FROM DATA
7 WHERE substr(NAME, 1, 1) = '_'
8 /
ID NAME
---------- ------
3 _name3
SQL>
Try this..
select * from table1 where regexp_like (name,'^_') ;

how to add different column with one code into one column?

id text
1 hi
1 how are u
1 fine ?
2 rad
2 qey
3
I am searching for a query where it can let me insert id = 1 to another table in one column
id test
1 hi how are you fine ?
2 rad qey
with the listagg function , i will have such result : hi how are you fine ? .. can i have it such
hi
how are u
fine ?
This query can be used to achieve your result:
WITH tab(id,text) AS (
SELECT 1, 'hi' FROM dual UNION ALL
SELECT 1, 'how are u' FROM dual UNION ALL
SELECT 1, 'fine ?' FROM dual UNION ALL
SELECT 2, 'rad' FROM dual UNION ALL
SELECT 2, 'qey' FROM dual UNION ALL
SELECT 3, NULL FROM dual)
-----
--End of data
-----
SELECT ID,
listagg(text, ' ') within GROUP (ORDER BY ROWNUM) AS text
FROM tab
GROUP BY ID;
Output
ID TEXT
1 hi how are u fine ?
2 rad qey
3
But there is one problem, although order by rownum is used but you cannot guaranty the order of text, its better to have one more column in your table that can define the order of the text as below
id text order_text
1 hi 1
1 how are u 2
1 fine ? 3
2 rad 1
2 qey 2
3 1
and the use the query as
SELECT ID,
listagg(text, ' ') within GROUP (ORDER BY order_text) AS text
FROM tab
GROUP BY ID;
You can try the LISTAGG function.
For more information, see here.

Invalid number of returned comma delimited string in a IN clause in Oracle

I am trying to use a subquery that returns a comma delimited string in a IN clause.
The following way:
SELECT p.person_id, g.name || '>>' || p.firstname || ' ' || p.lastname as GROUP_PATH
FROM PERSON p
LEFT JOIN GROUP g ON (
g.group_id = p.group_id
)
WHERE p.person_id IN (
SELECT person_ids FROM other WHERE other_id = :OTHER_ID
)
ORDER BY lower(GROUP_PATH)
And I am getting the following error:
ORA-01722: invalid number.
Is there a better way to do this or even possible?
The most obvious explanation is that you are trying to do math with a string...
The attempted conversion of a
character string to a number failed
because the character string was not a
valid numeric literal. Only numeric
fields or character fields containing
numeric data may be used in arithmetic
functions or expressions. Only numeric
fields may be added to or subtracted
from dates.
http://ora-01722.ora-code.com/
Update #1:
Your description worries me:
I am trying to use a subquery that
returns a comma delimited string in a
IN clause.
Your subquery should not return a comma delimited string (unless g.group_id is a string and expects a comma delimited string). You must retrieve individual items in as many rows as needed (less than 1,000 anyway).
Update #2:
Just to make it clear:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO;
FOO_ID
----------------------
1
2
3
You can do this:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN (1, 2);
FOO_ID
----------------------
1
2
But not this:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN ('1,2');
SQL Error: ORA-01722: invalid number
Because you cannot compare number 1 with string '1,2'. Subqueries follow similar rules:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL
);
FOO_ID
----------------------
1
2
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN (
SELECT '1,2' AS FOO_ID FROM DUAL
);
SQL Error: ORA-01722: invalid number
At a minimum, in order to reference the alias GROUP_PATH, you would need to need to use a nested subquery where the alias is defined before you reference it in your ORDER BY clause. That's realistically not causing the ORA-01722 error, but it is a problem
SELECT group_id, group_path
FROM (SELECT g.group_id,
g.name || '>>' || p.firstname || ' ' || p.lastname as GROUP_PATH
FROM PERSON p
LEFT JOIN GROUP g ON (
g.group_id = p.group_id
)
WHERE p.person_id IN (
SELECT person_ids FROM other WHERE other_id = :OTHER_ID
)
ORDER BY lower(GROUP_PATH)
If the PERSON_IDS column in the OTHER table is a comma separated list of values, your IN list is not going to do what you'd expect. You would need to transform the scalar string (that happens to have commas in it) into some sort of collection of multiple PERSON_ID values. There are various approaches to doing this, Tom Kyte has one example of using a variable IN list. Assuming you copy Tom's IN_LIST function, you should be able to do something like
SELECT group_id, group_path
FROM (SELECT g.group_id,
g.name || '>>' || p.firstname || ' ' || p.lastname as GROUP_PATH
FROM PERSON p
LEFT JOIN GROUP g ON (
g.group_id = p.group_id
)
WHERE p.person_id IN (
SELECT column_value
FROM TABLE(SELECT in_list(person_ids)
FROM other
WHERE other_id = :OTHER_ID)
)
ORDER BY lower(GROUP_PATH)

how to replace multiple strings together in Oracle

I have a string coming from a table like "can no pay{1},as your payment{2}due on {3}". I want to replace {1} with some value , {2} with some value and {3} with some value .
Is it Possible to replace all 3 in one replace function ? or is there any way I can directly write query and get replaced value ? I want to replace these strings in Oracle stored procedure the original string is coming from one of my table I am just doing select on that table
and then I want to replace {1},{2},{3} values from that string to the other value that I have from another table
Although it is not one call, you can nest the replace() calls:
SET mycol = replace( replace(mycol, '{1}', 'myoneval'), '{2}', mytwoval)
If there are many variables to replace and you have them in another table and if the number of variables is variable you can use a recursive CTE to replace them.
An example below. In table fg_rulez you put the strings with their replacement. In table fg_data you have your input strings.
set define off;
drop table fg_rulez
create table fg_rulez as
select 1 id,'<' symbol, 'less than' text from dual
union all select 2, '>', 'great than' from dual
union all select 3, '$', 'dollars' from dual
union all select 4, '&', 'and' from dual;
drop table fg_data;
create table fg_Data AS(
SELECT 'amount $ must be < 1 & > 2' str FROM dual
union all
SELECT 'John is > Peter & has many $' str FROM dual
union all
SELECT 'Eliana is < mary & do not has many $' str FROM dual
);
WITH q(str, id) as (
SELECT str, 0 id
FROM fg_Data
UNION ALL
SELECT replace(q.str,symbol,text), fg_rulez.id
FROM q
JOIN fg_rulez
ON q.id = fg_rulez.id - 1
)
SELECT str from q where id = (select max(id) from fg_rulez);
So, a single replace.
Result:
amount dollars must be less than 1 and great than 2
John is great than Peter and has many dollars
Eliana is less than mary and do not has many dollars
The terminology symbol instead of variable comes from this duplicated question.
Oracle 11gR2
Let's write the same sample as a CTE only:
with fg_rulez as (
select 1 id,'<' symbol, 'less than' text from dual
union all select 2, '>', 'greater than' from dual
union all select 3, '$', 'dollars' from dual
union all select 4, '+', 'and' from dual
), fg_Data AS (
SELECT 'amount $ must be < 1 + > 2' str FROM dual
union all
SELECT 'John is > Peter + has many $' str FROM dual
union all
SELECT 'Eliana is < mary + do not has many $' str FROM dual
), q(str, id) as (
SELECT str, 0 id
FROM fg_Data
UNION ALL
SELECT replace(q.str,symbol,text), fg_rulez.id
FROM q
JOIN fg_rulez
ON q.id = fg_rulez.id - 1
)
SELECT str from q where id = (select max(id) from fg_rulez);
If the number of values to replace is too big or you need to be able to easily maintain it, you could also split the string, use a dictionary table and finally aggregate the results
In the example below I'm assuming that the words in your string are separated with blankspaces and the wordcount in the string will not be bigger than 100 (pivot table cardinality)
with Dict as
(select '{1}' String, 'myfirstval' Repl from dual
union all
select '{2}' String, 'mysecondval' Repl from dual
union all
select '{3}' String, 'mythirdval' Repl from dual
union all
select '{Nth}' String, 'myNthval' Repl from dual
)
,MyStrings as
(select 'This is the first example {1} ' Str, 1 strnum from dual
union all
select 'In the Second example all values are shown {1} {2} {3} {Nth} ', 2 from dual
union all
select '{3} Is the value for the third', 3 from dual
union all
select '{Nth} Is the value for the Nth', 4 from dual
)
-- pivot is used to split the stings from MyStrings. We use a cartesian join for this
,pivot as (
Select Rownum Pnum
From dual
Connect By Rownum <= 100
)
-- StrtoRow is basically a cartesian join between MyStings and Pivot.
-- There as many rows as individual string elements in the Mystring Table
-- (Max = Numnber of rows Mystring table * 100).
,StrtoRow as
(
SELECT rownum rn
,ms.strnum
,REGEXP_SUBSTR (Str,'[^ ]+',1,pv.pnum) TXT
FROM MyStrings ms
,pivot pv
where REGEXP_SUBSTR (Str,'[^ ]+',1,pv.pnum) is not null
)
-- This is the main Select.
-- With the listagg function we group the string together in lines using the key strnum (group by)
-- The NVL gets the translations:
-- if there is a Repl (Replacement from the dict table) then provide it,
-- Otherwise TXT (string without translation)
Select Listagg(NVL(Repl,TXT),' ') within group (order by rn)
from
(
-- outher join between strings and the translations (not all strings have translations)
Select sr.TXT, d.Repl, sr.strnum, sr.rn
from StrtoRow sr
,dict d
where sr.TXT = d.String(+)
order by strnum, rn
) group by strnum
If you are doing this inside of a select, you can just piece it together, if your replacement values are columns, using string concatenation.

Resources