Oracle SQL substr/instr solution required - oracle

I need help with an SQL statement I am trying to run, I have done quite a bit of reading and testing but I cannot get the correct results hence my request here.
I am trying to extract data from a column that has had data concatenated from two sources, I want to seperate the data before & after the join, the join is a ' - ' (hyphen with a space either side), there can be alphanumeric chars before or after that ' - ' and this is the data I need. Just to add to the complexity some rows do not have joined data, i.e. there is no ' - ' and when this is met it is ok to simply extract the whole column value and treat it as the BB side (see first BB example below).
So when I have finished I would like to have seperated out the AA side (before the -) and the BB side (after the -) allowing for the single BB situation.
Looking at the data the following scenarious can occur.
BB<br>
AA - BB<br>
AA-aa - BB<br>
AA - BB-bb-cc<br>
AA-aa - BB-bb-cc<br>
I can get code to work but not consistently for all of the above scenarious - can you suggest the right code or even if there is a better solution which does not impact SQL performance.
Examples I have been trying for AA side:-
WORKS:
select substr('AA - BB-bb', 0, instr('AA - BB-bb', ' - ', 1, 1)-1) AS A_NAME
from DUAL;
FAILS (Only gets AA, not AA-aa):
select substr('AA-aa - BB-bb', 0,instr('AA-aa - BB-bb', ' - ', 1, 1)-1) AS A_NAME
from DUAL;
Examples I have been trying for BB side:-
FAILS:
select SUBSTR('AA-aa - WHENEHEH', INSTR('AA-aa - WHENEHEH',' - ', -1, 1)+1, 100)
B_NAME from dual;
thanks, Mark.

If your seperator is sure gona be a "space on either side of a hypen"
Then you can use CHR() function.
From your try
select substr('AA-aa - BB-bb', 0,instr('AA-aa - BB-bb', ' - ', 1, 1)-1) AS A_NAME
from DUAL;
Use something like this
select substr('AA-aa - BB-bb',0,instr('AA-aa - BB-bb',chr(32))-1) from dual; /* ASCII value for space is chr(32) /*
O/P
AA-aa
select substr('AA-aa - BB-bb',instr('AA-aa - BB-bb',chr(32))+2,10) from dual;
O/P
" - BB"

Assuming that, as shown in your list of scenarios, the pattern '- BB' can be relied on, you can use that to find the split.
WITH
DATA AS (
SELECT 'BB' text FROM dual
UNION ALL SELECT 'AA - BB' FROM dual
UNION ALL SELECT 'AA-aa - BB' FROM dual
UNION ALL SELECT 'AA - BB-bb-cc' FROM dual
UNION ALL SELECT 'AA-aa - BB-bb-cc' FROM dual
),
break AS (
SELECT text, instr(text, '- BB') breakpos FROM DATA
)
SELECT
text,
CASE WHEN breakpos = 0 THEN NULL ELSE substr( text, 1, breakpos-1 ) END aa_side,
case when breakpos = 0 then text else substr( text, breakpos+2) end bb_side
FROM break
;

Here is something that I think will work for you. (I hope I understood your requirement right)
I tried the query with all the examples you had mentioned and it works good.
SELECT REGEXP_SUBSTR ('AA-aa - BB-bb-cc',
'[^ - ]+')
"Right Side",
SUBSTR ('AA-aa - BB-bb-cc',
INSTR ('AA-aa - BB-bb-cc',
' - ')
+ 2)
"Left Side"
FROM DUAL;
This one works for everything but for BB and as a work around for that you can check if the delimiter exist and if it doesn't you can take the entire value for the right side.
SELECT SUBSTR ('AA aa - BB',0,
INSTR ('AA aa - BB',
' - ')
)
"Right Side",
SUBSTR('AA aa - BB',INSTR('AA aa - BB',
' - ')+2)
"Left Side"
FROM DUAL;

Your condition of a single value show up in second column instead of first is a bit odd, but this should do everything else you require:
with testdata as (
select 'BB' as input_col, 1 as row_num from dual
union
select 'AA - BB', 2 from dual
union
select 'AA-aa - BB',3 from dual
)
select testdata.row_num,
case when (regexp_instr(testdata.input_col,'(\ -\ )') > 0) then
regexp_replace(testdata.input_col, '(.*)(\ -\ )(.*)$', '\1')
else null
end output_col1,
regexp_replace(testdata.input_col, '(.*)(\ -\ )(.*)$', '\3') as output_col2
from testdata
order by testdata.row_num;
Edit: I modified the above to add the case check. I admit there is probably a more eloquent way to do this via the regexp_replace itself, but this works too.

Related

Substring of a string using Oracle (SUBSTRING or REGEXP_SUBSTR) When there is multiple Matches

Hi I am trying to fetch substring in oracle for the below two strings. I want result if the pattern matches starting with S9C and the having next numbers
For Eg: for the below two inputs I need output like
Input:
1.CABLE : THERMINAL 3X2X0.25MM FPCP PLUS UNITRONIC S9C000019651
2.Motor Protection Relay EMR-3MPO-2S9CB1-1 (was IQ1000II / MP 3000)
3.GREASE : BEM 41-132 3KG CARTRIDGE KLUBERPLEX S9C00019171 (Order by KG's required)
4.DO NOT USE CARRIER SPIDEX ZK 38 98 SH. S9C00011593 (SUPERSEDE BY S9C10204555 - WIN0020775)
Output:
1.S9C000019651
2.Null
3.S9C00019171
4.S9C10204555
Or Else the Better way is to fetch first matching String from back of the text.
I think you can achieve the desired result with below script where you can pick the second group. If that is null, Pick the first group -
WITH DATA AS (SELECT 'CABLE : THERMINAL 3X2X0.25MM FPCP PLUS UNITRONIC S9C000019651' STR FROM DUAL
UNION ALL
SELECT 'Motor Protection Relay EMR-3MPO-2S9CB1-1 (was IQ1000II / MP 3000)' FROM DUAL
UNION ALL
SELECT 'GREASE : BEM 41-132 3KG CARTRIDGE KLUBERPLEX S9C00019171 (Order by KG''s required)' FROM DUAL
UNION ALL
SELECT 'DO NOT USE CARRIER SPIDEX ZK 38 98 SH. S9C00011593 (SUPERSEDE BY S9C10204555 - WIN0020775)' FROM DUAL)
SELECT NVL(REGEXP_SUBSTR(STR, 'S9C\d{7,}', 1, 2), REGEXP_SUBSTR(STR, 'S9C\d{7,}'))
FROM DATA;
Demo.

How to add new line separator into data for oracle

I'm working on displaying data from oracle.
is there a way to make the following data inside the table:
example :
'1.somedata, 2.somedata, 3.somedata, 4.somedata, 5.somedata'
to display like:
example:
'1. somedata
2. somedata
3. somedata
4. somedata
5. somedata'
on the interface?
do i add new line separator directly into the data?
or do i separator them into new line when i query it?
or is there any other simple way?
Thanks.
There are so many ways to do this, here is one if you are selecting from a column:
SELECT REPLACE ('1.somedata, 2.somedata, 3.somedata, 4.somedata, 5.somedata', ',', CHR (13) || CHR (10)) AS split
FROM DUAL;
1.somedata
2.somedata
3.somedata
4.somedata
5.somedata
I personally would use the listagg function and use '' as the delimiter.
SELECT LISTAGG(last_name, ' ')
WITHIN GROUP (ORDER BY hire_date, last_name) "Emp_list",
MIN(hire_date) "Earliest"
FROM employees
WHERE department_id = 30;
Remember that Apex is generating a web page, which means the end result is HTML. Apex, however, will also sometimes escape special HTML characters for you, like < and &. Since you're viewing a table, I assume the source of your data is a query and your "somedata" field is a single column. Try this:
SELECT REPLACE( somedata_column, ',', '<br />' )
FROM mytable
You don't say what version of Apex. In Apex 4.x, the column would need to be set to a Standard Report Column, which would stop Apex from the <br> elements. I forget what the column type is in Apex 5.x.
Check below sample query which converts coma separated list data into rows
SELECT substr( '1.AL,2.AL,3.AL,4.AL,5.AL,6.AL,',
( case when rownum = 1 then 1
else instr( '1.AL,2.AL,3.AL,4.AL,5.AL,6.AL,', ',', 1, rownum - 1 ) + 1
end ),
instr( substr( '1.AL,2.AL,3.AL,4.AL,5.AL,6.AL,',
( case when rownum = 1 then 1
else instr( '1.AL,2.AL,3.AL,4.AL,5.AL,6.AL,', ',', 1, rownum - 1 ) + 1
end )
), ',' ) - 1
) as data
FROM dual
CONNECT BY LEVEL <= length( '1.AL,2.AL,3.AL,4.AL,5.AL,6.AL,' ) - length ( replace('1.AL,2.AL,3.AL,4.AL,5.AL,6.AL,', ',') )
Hope this will help you!

String Manipulation in Oracle 11g

I am currently working on an exercise that will display a certain text, say TESTTEMP, from a series of characters stored in a column. Example of the string:
PROGRAMNAME|MAX2112ETI_L;PROGRAMREV|L;TESTOPTION|FR;TESTTEMP|25;STD IPH|528.63436123348
Now what I need is to extract the text past the string TESTTEMP| and before the ;. What I did is I extracted all the text past TESTTEMP| and just get the first two character. Unfortunately, this will not be possible since there are cases where in the TESTTEMP| has 3 characters. Is there a way for me to be able to do this? Below is what I have so far:
SELECT
SUBSTR(SUBSTR(value, INSTR(value, ';TESTTEMP|')+10), 1, 2) invalue
FROM
(
SELECT
'PROGRAMNAME|MAX2112ETI_L;PROGRAMREV|L;TESTOPTION|FR;TESTTEMP|25;STD IPH|528.63436123348' VALUE
FROM dual
)t;
Find the index of ; in the substring and use that as index instead of hard coding it to 2.
SELECT
SUBSTR(SUBSTR(value, INSTR(value, ';TESTTEMP|')+10), 1, INSTR(SUBSTR(value, INSTR(value, ';TESTTEMP|')+10), ';')-1) invalue
FROM
(
SELECT
'PROGRAMNAME|MAX2112ETI_L;PROGRAMREV|L;TESTOPTION|FR;TESTTEMP|25;STD IPH|528.63436123348' VALUE
FROM dual
)t;
If that look messy, you can even write it like this
SELECT
SUBSTR(VALUE, 1, INSTR(VALUE, ';') - 1) invalue
FROM
(
SELECT
SUBSTR(VALUE, INSTR(VALUE, ';TESTTEMP|') + 10)
VALUE
FROM (
SELECT
'PROGRAMNAME|MAX2112ETI_L;PROGRAMREV|L;TESTOPTION|FR;TESTTEMP|25;STD IPH|528.63436123348'
VALUE
FROM dual) t
)t;
Based on comments - if you want to know if the string was found or not, use this query. FOUND will be greater than 0 if the string is found.
SELECT
SUBSTR(VALUE, 1, INSTR(VALUE, ';')-1) invalue, FOUND
FROM
(
SELECT
SUBSTR(VALUE, INSTR(VALUE, SEARCHSTRING)+10) VALUE, INSTR(VALUE, SEARCHSTRING) FOUND
FROM (
SELECT
'PROGRAMNAME|MAX2112ETI_L;PROGRAMREV|L;TESTOPTION|FR;TESTTEMP|25;STD IPH|528.63436123348'
VALUE,
';TESTTEMP|' SEARCHSTRING
FROM dual) t
)t;

PL/SQL query IN comma deliminated string

I am developing an application in Oracle APEX. I have a string with user id's that is comma deliminated which looks like this,
45,4932,20,19
This string is stored as
:P5_USER_ID_LIST
I want a query that will find all users that are within this list my query looks like this
SELECT * FROM users u WHERE u.user_id IN (:P5_USER_ID_LIST);
I keep getting an Oracle error: Invalid number. If I however hard code the string into the query it works. Like this:
SELECT * FROM users u WHERE u.user_id IN (45,4932,20,19);
Anyone know why this might be an issue?
A bind variable binds a value, in this case the string '45,4932,20,19'. You could use dynamic SQL and concatenation as suggested by Randy, but you would need to be very careful that the user is not able to modify this value, otherwise you have a SQL Injection issue.
A safer route would be to put the IDs into an Apex collection in a PL/SQL process:
declare
array apex_application_global.vc_arr2;
begin
array := apex_util.string_to_table (:P5_USER_ID_LIST, ',');
apex_collection.create_or_truncate_collection ('P5_ID_COLL');
apex_collection.add_members ('P5_ID_COLL', array);
end;
Then change your query to:
SELECT * FROM users u WHERE u.user_id IN
(SELECT c001 FROM apex_collections
WHERE collection_name = 'P5_ID_COLL')
An easier solution is to use instr:
SELECT * FROM users u
WHERE instr(',' || :P5_USER_ID_LIST ||',' ,',' || u.user_id|| ',', 1) !=0;
tricks:
',' || :P5_USER_ID_LIST ||','
to make your string ,45,4932,20,19,
',' || u.user_id|| ','
to have i.e. ,32, and avoid to select the 32 being in ,4932,
I have faced this situation several times and here is what i've used:
SELECT *
FROM users u
WHERE ','||to_char(:P5_USER_ID_LIST)||',' like '%,'||to_char(u.user_id)||',%'
ive used the like operator but you must be a little carefull of one aspect here: your item P5_USER_ID_LIST must be ",45,4932,20,19," so that like will compare with an exact number "',45,'".
When using it like this, the select will not mistake lets say : 5 with 15, 155, 55.
Try it out and let me know how it goes;)
Cheers ,
Alex
Create a native query rather than using "createQuery/createNamedQuery"
The reason this is an issue is that you cannot just bind an in list the way you want, and just about everyone makes this mistake at least once as they are learning Oracle (and probably SQL!).
When you bind the string '32,64,128', it effectively becomes a query like:
select ...
from t
where t.c1 in ('32,64,128')
To Oracle this is totally different to:
select ...
from t
where t.c1 in (32,64,128)
The first example has a single string value in the in list and the second has a 3 numbers in the in list. The reason you get an invalid number error is because Oracle attempts to cast the string '32,64,128' into a number, which it cannot do due to the commas in the string.
A variation of this "how do I bind an in list" question has come up on here quite a few times recently.
Generically, and without resorting to any PLSQL, worrying about SQL Injection or not binding the query correctly, you can use this trick:
with bound_inlist
as
(
select
substr(txt,
instr (txt, ',', 1, level ) + 1,
instr (txt, ',', 1, level+1) - instr (txt, ',', 1, level) -1 )
as token
from (select ','||:txt||',' txt from dual)
connect by level <= length(:txt)-length(replace(:txt,',',''))+1
)
select *
from bound_inlist a, users u
where a.token = u.id;
If possible the best idea may be to not store your user ids in csv! Put them in a table or failing that an array etc. You cannot bind a csv field as a number.
Please dont use: WHERE ','||to_char(:P5_USER_ID_LIST)||',' like '%,'||to_char(u.user_id)||',%' because you'll force a full table scan although with the users table you may not have that many so the impact will be low but against other tables in an enterprise environment this is a problem.
EDIT: I have put together a script to demonstrate the differences between the regex method and the wildcard like method. Not only is regex faster but it's also a lot more robust.
-- Create table
create table CSV_TEST
(
NUM NUMBER not null,
STR VARCHAR2(20)
);
create sequence csv_test_seq;
begin
for j in 1..10 loop
for i in 1..500000 loop
insert into csv_test( num, str ) values ( csv_test_seq.nextval, to_char( csv_test_seq.nextval ));
end loop;
commit;
end loop;
end;
/
-- Create/Recreate primary, unique and foreign key constraints
alter table CSV_TEST
add constraint CSV_TEST_PK primary key (NUM)
using index ;
alter table CSV_TEST
add constraint CSV_TEST_FK unique (STR)
using index;
select sysdate from dual;
select *
from csv_test t
where t.num in ( Select Regexp_Substr('100001, 100002, 100003 , 100004, 100005','[^,]+', 1, Level) From Dual
Connect By Regexp_Substr('100001, 100002,100003, 100004, 100005', '[^,]+', 1, Level) Is Not Null);
select sysdate from dual;
select *
from csv_test t
where ('%,' || '100001,100002, 100003, 100004 ,100005' || ',%') like '%,' || num || ',%';
select sysdate from dual;
select *
from csv_test t
where t.num in ( Select Regexp_Substr('100001, 100002, 100003 , 100004, 100005','[^,]+', 1, Level) From Dual
Connect By Regexp_Substr('100001, 100002,100003, 100004, 100005', '[^,]+', 1, Level) Is Not Null);
select sysdate from dual;
select *
from csv_test t
where ('%,' || '100001,100002, 100003, 100004 ,100005' || ',%') like '%,' || num || ',%';
select sysdate from dual;
drop table csv_test;
drop sequence csv_test_seq;
Solution from Tony Andrews works for me. The process should be added to "Page processing" >> "After submit">> "Processes".
As you are Storing User Ids as String so You can Easily match String Using Like as Below
SELECT * FROM users u WHERE u.user_id LIKE '%'||(:P5_USER_ID_LIST)||'%'
For Example
:P5_USER_ID_LIST = 45,4932,20,19
Your Query Surely Will return Any of 1 User Id which Matches to Users table
This Will Surely Resolve Your Issue , Enjoy
you will need to run this as dynamic SQL.
create the entire string, then run it dynamically.

Oracle string replacement

I have a column in my oracle database which due reasons beyond my control contains a CSV string e.g.
Item a,Item b,Item c,Item d
I want to run an UPDATE statement to get rid of item c. Thus ending up with
Item a,Item b,Item d
How can I achieve this
You could use the Oracle REPLACE function:
UPDATE table
SET col = replace(col, 'item c', '')
You just need to be careful handling it as part of a CSV, e.g stripping a following comma. This could mean replacing 'item c,' first and then replacing 'item c' to capture both cases.
EDIT: ah, I might have misunderstood. My solution is based on removing a particular string from your CSV - if you are looking to always replace the 3rd item then Vincent's answer is the one you'll need
If you have a fairly recent version of Oracle (I believe regular expressions were introduced in Oracle 10), you can use REGEXP_REPLACE:
UPDATE table SET column = REGEXP_REPLACE(column,'[^\,]+,','',1,3)
(Also, please do violent things to the genius who stored CSV this way in a relational database.)
you could use a combination of INSTR and SUBSTR to remove the third field:
SQL> WITH a AS (SELECT 'Item a,Item b,Item c,Item d' col FROM dual)
2 SELECT substr(col, 1, instr(col, ',', 1, 2))
3 || substr(col, instr(col, ',', 1, 3) + 1) sub
4 FROM a;
SUB
--------------------
Item a,Item b,Item d

Resources