Split column in Oracle - oracle

I have a column in an Oracle Database which has something like this data
column1
/opt/log/data/abcd.efghi.jklmn.aaa.txt
/opt/log/data/abbbcd.efccghi.jkdsdflmn.abab.txt
/opt/log/data/nmvcnmcd.efjhjghi.jkvslmn.abcbc.txt
/opt/log/data/hjsdhj.hjfdhdf.hdfhjd.aghag.txt
/opt/log/data/dfhjfdhj.yureyer.qwtyq.hjahjh.txt
I want to split the data in such a way that
**firstdot seconddot thirdnfourthdot**
abcd efghi jklmn.aaa
abbbcd efccghi jkdsdflmn.abab
nmvcnmcd efjhjghi jkvslmn.abcbc
hjsdhj hjfdhdf hdfhjd.aghag
dfhjfdhj yureyer qwtyq.hjahjh
I can get the seconddot value by
select substr(column1,instr(column1,'.',1+1,instr(column1,'.',1,2)-instr(column1,'.',1,1)-1) as secondot
but I could not get the rest. Can you guys help.
Thanks a lot

Without regexp, you need to reply the same logic for every substring you need, every timi picking the initial position and the leght, based on the position of the "terminator" of that substring.
/* input data */
with yourTable(column1) as (
select '/opt/log/data/abcd.efghi.jklmn.aaa.txt' from dual union all
select '/opt/log/data/abbbcd.efccghi.jkdsdflmn.abab.txt' from dual union all
select '/opt/log/data/nmvcnmcd.efjhjghi.jkvslmn.abcbc.txt' from dual union all
select '/opt/log/data/hjsdhj.hjfdhdf.hdfhjd.aghag.txt' from dual union all
select '/opt/log/data/dfhjfdhj.yureyer.qwtyq.hjahjh.txt' from dual
)
/* query */
select substr(column1, instr(column1, '/', -1) +1, instr(column1, '.') - instr(column1, '/', -1)-1) firstDot,
substr(column1, instr(column1, '.') +1, instr(column1, '.', 1, 2) - instr(column1, '.') -1) secondDot,
substr(column1, instr(column1, '.', 1, 2) +1, instr(column1, '.', 1, 4) - instr(column1, '.', 1, 2) -1) thirdAndFourthDot
from yourTable
gives:
FIRSTDOT SECONDDOT THIRDANDFOURTHD
--------------- --------------- ---------------
abcd efghi jklmn.aaa
abbbcd efccghi jkdsdflmn.abab
nmvcnmcd efjhjghi jkvslmn.abcbc
hjsdhj hjfdhdf hdfhjd.aghag
dfhjfdhj yureyer qwtyq.hjahjh
In a more readable way:
select substr(column1, lastSlashPos +1, firstDotPos - lastSlashPos -1) as firstDot,
substr(column1, firstDotPos +1, secondDotPos - firstDotPos -1) as secondDot,
substr(column1, secondDotPos +1, fourthDotPos - secondDotPos -1) as thirdAndFourthDot
from (
select instr(column1, '/', -1) as lastSlashPos,
instr(column1, '.') as firstDotPos,
instr(column1, '.', 1, 2) as secondDotPos,
instr(column1, '.', 1, 3) as thirdDotPos,
instr(column1, '.', 1, 4) as fourthDotPos,
column1
from yourTable
)

select substr('/opt/log/data/abcd.efghi.jklmn.aaa.txt',instr('/opt/log/data/abcd.efghi.jklmn.aaa.txt','/',-1) + 1) from dual;
This will give you text after last /
Then you need to apply instr for .:
select
substr(text, 1, instr(text,'.', 1) - 1),
substr(text, instr(text,'.', 1) + 1, instr(text,'.', 2) - 1),
substr(text, instr(text,'.', 2) + 1)
from (
select substr('/opt/log/data/abcd.efghi.jklmn.aaa.txt',instr('/opt/log/data/abcd.efghi.jklmn.aaa.txt','/',-1) + 1) text from dual
);

Related

How can I split by a character without ignoring nulls?

I'm trying to use regexp_subst to split a delimited string. I'm running into an issue when delimited fields are null. The regexp_substr ignores the nulls and moves to the next occurrence of the delimiter. Is there a way to do this with regexp_substr? If not, what alternative do you use?
--Expecting hello, gets hello
select regexp_substr('hello##world', '[^#]+', 1, 1)
from dual;
--Expecting null, gets world
select regexp_substr('hello##world', '[^#]+', 1, 2)
from dual;
--Expecting world, gets null
select regexp_substr('hello##world', '[^#]+', 1, 3)
from dual;
EDIT: tried this, but it works only with | which isn't an option
Answering based on Matbailie's input in above comment
select regexp_substr('hello##world', '(.*?)(#|$)', 1, 1,NULL,1)
from dual
union all
--Expecting null, gets null
select regexp_substr('hello##world', '(.*?)(#|$)', 1, 2,NULL,1)
from dual
union all
--Expecting world, gets world
select regexp_substr('hello##world', '(.*?)(#|$)', 1, 3,NULL,1)
from dual;
You do not need regular expressions. It can be done with simple (and faster) string functions in a recursive sub-query:
WITH data (value) AS (
SELECT 'hello##world' FROM DUAL
),
bounds (value, start_pos, end_pos) AS (
SELECT value,
1,
INSTR(value, '#', 1)
FROM data
UNION ALL
SELECT value,
end_pos + 1,
INSTR(value, '#', end_pos + 1)
FROM bounds
WHERE end_pos > 0
)
SEARCH DEPTH FIRST BY value SET order_id
SELECT CASE end_pos
WHEN 0
THEN SUBSTR(value, start_pos)
ELSE SUBSTR(value, start_pos, end_pos - start_pos)
END AS item
FROM bounds;
Which outputs:
ITEM
hello
null
world
Or, if you want the data in columns (rather than rows):
WITH data (value) AS (
SELECT 'hello##world' FROM DUAL
),
bounds (value, pos1, pos2) AS (
SELECT value,
INSTR(value, '#', 1, 1),
INSTR(value, '#', 1, 2)
FROM data
)
SELECT SUBSTR(value, 1, pos1 - 1) AS item1,
SUBSTR(value, pos1 + 1, pos2 - pos1 - 1) AS item2,
SUBSTR(value, pos2 + 1) AS item3
FROM bounds
Which outputs:
ITEM1
ITEM2
ITEM3
hello
null
world
If you did want to use (slower) regular expressions then:
WITH data (value) AS (
SELECT 'hello##world' FROM DUAL
)
SELECT item
FROM data d
CROSS JOIN LATERAL(
SELECT REGEXP_SUBSTR( d.value, '(.*?)(#|$)', 1, LEVEL, NULL, 1) AS item
FROM DUAL
CONNECT BY LEVEL < REGEXP_COUNT( d.value, '(.*?)(#|$)')
)
or, for columns:
WITH data (value) AS (
SELECT 'hello##world' FROM DUAL
)
SELECT REGEXP_SUBSTR(value, '(.*?)(#|$)', 1, 1, NULL, 1) AS item1,
REGEXP_SUBSTR(value, '(.*?)(#|$)', 1, 2, NULL, 1) AS item2,
REGEXP_SUBSTR(value, '(.*?)(#|$)', 1, 3, NULL, 1) AS item3
FROM data
(Which both have the same output as above)
db<>fiddle here

Trim String in oracle

I have below string & want only state name in output, kindly suggest me.
'State - Rajasthan,Zone - Jaipur-I,Circle - Circle-P, Jaipur,Ward - Circle-P, jaipur - Ward-2'
Desired Output- Rajasthan
According to that example, here are a few options you might consider:
SQL> with test (col) as
2 (select 'State - Rajasthan,Zone - Jaipur-I,Circle - Circle-P, Jaipur,Ward - Circle-P, jaipur - Ward-2'
3 from dual
4 )
5 select substr(col, instr(col, ' ', 1, 2) + 1,
6 instr(col, ',') - instr(col, ' ', 1, 2) - 1
7 ) state,
8 --
9 replace(substr(col, 1, instr(col, ',') - 1), 'State - ', '') state2,
10 --
11 regexp_substr(col, '\w+', 1, 2) state3
12 from test;
STATE STATE2 STATE3
--------- --------- ---------
Rajasthan Rajasthan Rajasthan
SQL>

Extract hours from time

I am try to find solution but I couldn't. The problem is following.
1 ) I want to extract hours from time and add minutes converted to hours
(SUM(SUBSTRING_INDEX(aa.Quantity, ':', 1)) + TRUNCATE((SUM(SUBSTRING_INDEX(aa.Quantity, ':', -1)) / 60),0))
So for example If I have 16hours:35minutes. I want to display 16 and minutes part should be added to hours as 16,5 hours for example
2) extract minutes from time and find reminder (modulo)
LPAD((SUM(SUBSTRING_INDEX(aa.Quantity, ':', -1)) % 60), 2, 0)
I found this part of solution but this soulution is wrote in MySQL and I need Oracle SQL solution
CONCAT(
-- extract hours froAm time and add minutes converted to hours
(SUM(SUBSTRING_INDEX(aa.Quantity, ':', 1)) + TRUNCATE((SUM(SUBSTRING_INDEX(aa.Quantity, ':', -1)) / 60),0))
, ':',
-- extract minutes from time and find reminder (modulo)*/
LPAD((SUM(SUBSTRING_INDEX(aa.Quantity, ':', -1)) % 60), 2, 0)
) AS W_TOTAL_SUM
Also I try to convert this MySQL statment to Oracle SQL by using following site, but unfortunettly I didn't get correct result since it returns same output as input
http://www.sqlines.com/online
So exactly same as I decribe but only in Oracle SQL. I would be very thankfull since I try to fix this problem a couple of hours and couldn't find any solution for this problem
Here is my solution which doesn't work. I get error
ORA-00907: missing right parenthesis
SELECT
(SUM(SUBSTR(A.Quantity, ':', 1)) + TRUNC((SUM(SUBSTR(A.Quantity, ':', -1)) / 60),0)), ':' ,
MOD(LPAD(SUM(SUBSTR(A.Quantity, ':', -1)), 60),2,0)
-- MOD(LPAD((SUM(SUBSTRING_INDEX(A.Quantity, ':', -1)) % 60), 2, 0)
-- LPAD((SUM(SUBSTRING_INDEX(aa.Quantity, ':', -1)) % 60), 2, 0)
AS TOTAL_SUM FROM (
SELECT
ata.ATAID AS AtaId, ata.ProjectID, ata.StartDate, ataAW.Quantity
FROM
ata
INNER JOIN
weekly_report
ON
weekly_report.ataId = ata.ATAID
INNER JOIN
ata_articles ataAW
ON
ataAW.wrId = weekly_report.id
WHERE
ata.ATAID = 10987
AND
ataAW.type = 1
OR
ataAW.type = 2
OR
ataAW.type = 3
AND
(weekly_report.status != 3 AND weekly_report.status != 4)
AND
(
weekly_report.year < (SELECT year FROM weekly_report WHERE id = 89)
OR
(
weekly_report.year <= (SELECT year FROM weekly_report WHERE id = 89)
AND
weekly_report.week <= (SELECT week FROM weekly_report WHERE id = 89)
)
)
) A
group by A.AtaId
order by A.AtaId ASC
The common problem is
LPAD((SUM(SUBSTR(A.Quantity, ':', -1)) MOD 60), 2, 0)
Here is output which I expect
TOTAL_SUM
101:24
This is how I understood the question:
sample data in lines #1 - 2
extract hours (line #3)
extract minutes (line #5), divide it by 60 (as number of minutes in an hour)
hours + minutes = result
SQL> with test (col) as
2 (select '16hours:35minutes' from dual)
3 select to_number(regexp_substr(col, '\d+', 1, 1)) -- hours
4 +
5 round(to_number(regexp_substr(col, '\d+', 1, 2)) / 60, 2) -- minutes
6 as result
7 from test
8 /
RESULT
----------
16,58
SQL>

Parsing strings regular expression for Oracle

select rtrim(regexp_substr (str, '[^|]*(.|$)', 1, level), '|') ASPLIT
from
(select 'str 1|str 2|str 3' as str from dual)
connect by level <= length (regexp_replace (str, '[^|]+')) + 1
str 1 str 2 str 3
How to alter the parser separator ', ' ?
'str 1, str 2, str 3'
You can just change the delimiter in the pattern:
select rtrim(regexp_substr (str, '[^,]*(.|$)', 1, level), ',') ASPLIT
from
(select 'str 1, str 2, str 3' as str from dual)
connect by level <= length (regexp_replace (str, '[^,]+')) + 1;
Note that you do not want to change the one in the grouping, (.|$); in that context it's an OR operator not a literal character.
It's simpler to use the same pattern in the substring as you do in the replace (but note Gary_W's warning about this losing empty values with this pattern):
select trim(regexp_substr (str, '[^,]+', 1, level)) ASPLIT
from (select 'str 1, str 2, str 3' as str from dual)
connect by level <= length (regexp_replace (str, '[^,]+')) + 1;
ASPLIT
-------------------
str 1
str 2
str 3
But since you have spaces after the commas, you need to eliminate those; the simplest way is to get rid of leading and trailing spaces with trim. This also shows a variation on the connect by limit but either works (again, note the warning about this pattern):
select trim(regexp_substr (str, '[^,]+', 1, level)) ASPLIT
from (select 'str 1, str 2, str 3' as str from dual)
connect by regexp_substr (str, '[^,]+', 1, level) is not null;
ASPLIT
-------------------
str 1
str 2
str 3
I must point out that using the regex of the format '[^,]+' to parse a string will give invalid results if there is a NULL element in the list and the position of the element in the list is important. Consider this where the 2nd element is NULL. The results make it seem the 2nd element is 'str 3' where really the 2nd element is NULL.
SQL> select trim(regexp_substr (str, '[^,]+', 1, level)) ASPLIT
from (select 'str 1,, str 3' as str from dual)
connect by level <= length (regexp_replace (str, '[^,]+')) + 1;
ASPLIT
-------------
str 1
str 3
Here's another way that handles the NULL list element:
SQL> select trim(regexp_substr (str, '(.*?)(,|$)', 1, level, NULL, 1)) ASPLIT
from (select 'str 1,, str 3' as str from dual)
connect by level <= regexp_count(str, ',') + 1;
ASPLIT
-------------
str 1
str 3
SQL>
See this post for more info too: Split comma separated values to columns in Oracle

Invalid number error - [Error Code: 1722, SQL State: 42000] ORA-01722: invalid number

The 1st query from the below 2 queries is giving me [Error Code: 1722, SQL State: 42000] ORA-01722: invalid number error.
But when I limit the no of records as in the 2nd query then it is running fine.
Other than limiting the rows in the 2nd query, both the queries are identical.
SELECT b.first_name,
b.last_name,
b.device_derived,
b.ios_version_group,
b.add_date,
FIRST_VALUE (b.add_date)
OVER (PARTITION BY b.first_name, b.last_name, b.ios_version_group)
AS first_date,
LAST_VALUE (b.add_date)
OVER (PARTITION BY b.first_name, b.last_name, b.ios_version_group)
AS last_date
FROM (SELECT a.first_name,
a.last_name,
a.os_version,
a.device_type,
a.device,
a.add_date,
a.device_derived,
CASE
WHEN ( ( UPPER (a.device_derived) = 'IPHONE'
OR UPPER (a.device_derived) = 'IPAD')
AND TO_NUMBER (SUBSTR (a.os_version, 1, 1)) > 4)
THEN
'iOS ' || SUBSTR (a.os_version, 1, 1)
ELSE
'Others'
END
AS ios_version_group
FROM (SELECT first_name,
last_name,
os_version,
device_type,
device,
add_date,
CASE
WHEN UPPER (device_type) = 'ANDROID'
THEN
'Android'
WHEN UPPER (device_type) = 'BB'
OR UPPER (device_type) = 'BLACKBERRY'
THEN
'Blackberry'
WHEN UPPER (device_type) = 'IOS'
AND ( SUBSTR (UPPER (device), 1, 6) = 'IPHONE'
OR SUBSTR (UPPER (device), 1, 4) = 'IPOD')
THEN
'iPhone'
WHEN UPPER (device_type) = 'IOS'
AND (SUBSTR (UPPER (device), 1, 4) = 'IPAD')
THEN
'iPad'
END
AS device_derived
FROM vw_mobile_devices_all) a) b;
SELECT b.first_name,
b.last_name,
b.device_derived,
b.ios_version_group,
b.add_date,
FIRST_VALUE (b.add_date)
OVER (PARTITION BY b.first_name, b.last_name, b.ios_version_group)
AS first_date,
LAST_VALUE (b.add_date)
OVER (PARTITION BY b.first_name, b.last_name, b.ios_version_group)
AS last_date
FROM (SELECT a.first_name,
a.last_name,
a.os_version,
a.device_type,
a.device,
a.add_date,
a.device_derived,
CASE
WHEN ( ( UPPER (a.device_derived) = 'IPHONE'
OR UPPER (a.device_derived) = 'IPAD')
AND TO_NUMBER (SUBSTR (a.os_version, 1, 1)) > 4)
THEN
'iOS ' || SUBSTR (a.os_version, 1, 1)
ELSE
'Others'
END
AS ios_version_group
FROM (SELECT first_name,
last_name,
os_version,
device_type,
device,
add_date,
CASE
WHEN UPPER (device_type) = 'ANDROID'
THEN
'Android'
WHEN UPPER (device_type) = 'BB'
OR UPPER (device_type) = 'BLACKBERRY'
THEN
'Blackberry'
WHEN UPPER (device_type) = 'IOS'
AND ( SUBSTR (UPPER (device), 1, 6) = 'IPHONE'
OR SUBSTR (UPPER (device), 1, 4) = 'IPOD')
THEN
'iPhone'
WHEN UPPER (device_type) = 'IOS'
AND (SUBSTR (UPPER (device), 1, 4) = 'IPAD')
THEN
'iPad'
END
AS device_derived
FROM vw_mobile_devices_all) a) b
WHERE ROWNUM <= 100;
Can somebody tell me why I am getting this error. Is there an efficient way to write this query?
You have TO_NUMBER (SUBSTR (a.os_version, 1, 1) in your queries, so presumably you're hitting data that doesn't have a number at the start of the os_version, when you request more than 100 rows.
You need to check your data.
This error happens when you try to convert a non-numeric value with TO_NUMBER.
In the second query the first 100 rows seem not to result into a.os_version to a non-numeric value.
Try a simple select vw_mobile_devices_all to find the non-numeric os_version. Figure out how you can work around the problem. Maybe you can query the os_version differently.

Resources