Finding the maximum value from part of a string - sql

I have the below data coming from DB. I would like to get the maximum of last digits after ".". For example data looks like this, where the last digits after last "." are 160410, 6, 16 etc.
I would like to get the "11.2.0.4.160419" output
11.2.0.4.160419
11.2.0.4.6
11.2.0.4.16
11.2.0.4.10
11.2.0.4.18
11.2.0.4.2
11.2.0.4.14
11.2.0.4.4
11.2.0.4.160119
11.2.0.4.3
11.2.0.4.15
11.2.0.4.9
11.2.0.4.17
11.2.0.4.8
11.2.0.4.5
11.2.0.4.7
11.2.0.4.1
11.2.0.4.151117
11.2.0.4.13
11.2.0.4.12
11.2.0.4.20
11.2.0.4.11
11.2.0.4.19
data before the "." are not same. It has various values. Infact the actual data is like this
DATABASE PATCH FOR EXADATA (JAN 2016 - 11.2.0.4.160119) : (22309110)
DATABASE PATCH FOR EXADATA (JAN 2016 - 11.2.0.4.16) : (22309111)
.
.
In this I am interested to get max of 160119.
-- Added
Sorry I am back again. We are looking for further where we need to get the result like this
11.2.0.4.160419
Meaning, the maximum of after "." , but when displaying display everything in between the parenthesis.
Actual data
'DATABASE PATCH FOR EXADATA (NOV 2015 - 11.2.0.4.151117)
DATABASE PATCH FOR EXADATA (APR2014 - 11.2.0.4.6) : (18293775)
DATABASE PATCH FOR EXADATA (APR2015 - 11.2.0.4.16) : (20449729)
desired output
(NOV 2015 - 11.2.0.4.151117)
I have this query working
with
inputs ( target_guid, description) as (
select t.target_guid, a.description from MGMT$OH_PATCH a, mgmt$oh_installed_targets oh,MGMT$TARGET_COMPONENTS c,MGMT$TARGET_FLAT_MEMBERS d, mgmt_targets t where t.target_type = 'oracle_dbmachine' and d.member_target_type = 'host' and d.aggregate_target_guid = t.target_guid and c.target_type = 'oracle_database' and c.host_name = d.member_target_name and a.host_name = c.host_name and a.target_guid = oh.oh_target_guid and oh.inst_target_type like '%database%' and a.description is not null and a.description like '%PATCH FOR EXADATA%' group by t.target_guid, a.description order by t.target_guid
)
select target_guid, max(to_number(regexp_substr(description, '.(\d*))', 1, 1, null, 1))) as version
from inputs group by target_guid;
with the output of
5DA0496CCCD42CA1099F1AD06216F3C0 160419
ED10DD7D4C62CEAA117E7B7E97883EC2 9
I need the output as
5DA0496CCCD42CA1099F1AD06216F3C0 11.2.0.4.160419
ED10DD7D4C62CEAA117E7B7E97883EC2 11.2.0.4.9
Can you please help?

You can extract the last digits using:
select regexp_substr(col, '[0-9]+$', 1, 1)
If you don't like depending on the greediness of Oracle regular expressions (which I can appreciate), you can use:
select trim(leading '.' from regexp_substr(col, '[.][0-9]+$', 1, 1))
You can get the maximum value by converting to a numeric and taking the max:
select max(cast(regexp_substr(col, '[0-9]+$', 1, 1) as number))
To get the full column:
select t.*
from (select t.*
from t
order by cast(regexp_substr(col, '[0-9]+$', 1, 1) as number) desc
) t
where rownum = 1;
Finally, for your particular data, there is a simpler solution:
select t.*
from (select t.*
from t
order by length(col) desc, col desc
) t
where rownum = 1;
However, this assumes that all the stuff before the final '.' is the same.

If the assumptions I detailed in my Comment to your original question are correct, then something like this should work:
with
inputs ( inp_str ) as (
select 'DATABASE PATCH FOR EXADATA (JAN 2016 - 11.2.0.4.160119) : (22309110)'
from dual union all
select 'DATABASE PATCH FOR EXADATA (JAN 2016 - 11.2.0.4.16) : (22309111)' from dual
)
select max(to_number(regexp_substr(inp_str, '.(\d*)\)', 1, 1, null, 1))) as max_something
from inputs;
The select statement is really just the last two lines; the rest is for testing purposes. Replace inp_str with your actual column name, inputs with your table name, and max_something with your desired output column name.
EDIT:
Here is a solution for the OP's restated problem (see request in comments).
with
inputs ( inp_str ) as (
select 'DATABASE PATCH FOR EXADATA (JAN 2016 - 11.2.0.4.160119) : (22309110)'
from dual union all
select 'DATABASE PATCH FOR EXADATA (JAN 2016 - 11.2.0.4.16) : (22309111)' from dual
)
select regexp_substr(inp_str, '\(([^)]+)\)', 1, 1, null, 1) as token
from inputs
where to_number(regexp_substr(inp_str, '.(\d*)\)', 1, 1, null, 1)) =
( select max(to_number(regexp_substr(inp_str, '.(\d*)\)', 1, 1, null, 1)))
from inputs
)
;
Output:
TOKEN
-------------------------
JAN 2016 - 11.2.0.4.160119
1 row selected.

Maybe check out Oracle regexp_like e.g. WHERE REGEXP_LIKE(first_name, EXPRESSION)

Related

Oracle String Conversion

Need help in converting the following string into a required format. I will have several values as below. Is there a easy way to do this using REGEXP or something better?
Current format coming from column A
Region[Envionment Lead|||OTC|||06340|||List Program|||TX|||Z3452|||Souther Region 05|||M7894|||California Divison|||Beginning]
Region[Coding Analyst|||BA|||04561|||Water Bridge|||CA|||M8459|||West Region 09|||K04956|||East Division|||Supreme]
Required Format of column A
Region[actingname=Envionment Lead,commonid=OTC,insturmentid=06340,commonname=List Program]
Region[actingname=Coding Analyst,commonid=BA,insturmentid=04561,commonname=Water Bridge]
revised data
**Column data**
Region[Coding Analyst|||BA|||reg pro|||04561|||08/16/2011|||Board member|||AZ|||06340|||Whiter Bridge|||CA|||M0673|||West Region 09|||K04956|||East Division|||Supreme]
**required Data**
{actingname=06340, actingid=M0673, insturmentid=BA, insturmentname=Coding Analyst, commonname=West Region 09, stdate=08/16/2011, linnumber=04561, linstate=CA, linname=Supreme}
The issue is getting the 10,11,12 and 15 position of the string. I can get anything below 10th position, but not 10 or more string position. Can you please guide me what i'm i missing here
'{actingname=\8,actingid=\11,insturmentid=\2,insturmentname=\1,commonname=\12, stdate=\5,linnumber=4,linstate=10,linname=15}'--Here 10,11,12 and 15 posistion are not being fethched
I used REGEXP_REPLACE
SELECT REGEXP_REPLACE(
'Region[Envionment Lead|||OTC|||06340|||List Program|||TX|||Z3452|||Souther Region 05|||M7894|||California Divison|||Beginning]',
'^Region\[([[:alpha:][:space:][:digit:]]*)\|\|\|([[:alpha:]]*)\|\|\|([[:digit:]]*)\|\|\|([[:alpha:][:space:][:digit:]]*).*',
'Region[actingname=\1,commonid=\2,instrumentid=\3,commonname=\4]') as replaced
FROM dual
or like an update it would be
UPDATE table1
SET col1 = REGEXP_REPLACE(
col1,
'^Region\[([[:alpha:][:space:][:digit:]]*)\|\|\|([[:alpha:]]*)\|\|\|([[:digit:]]*)\|\|\|([[:alpha:][:space:][:digit:]]*).*',
'Region[actingname=\1,commonid=\2,instrumentid=\3,commonname=\4]')
You can use regexp_substr and listagg consecutively
with t1(str1) as
(
select 'Region[Coding Analyst|||BA|||04561|||Water Bridge]' from dual
), t2(str2) as
(
select 'actingname,commonid,insturmentid,commonname' from dual
), t3 as
(
select regexp_substr(str1, '[^|||]+', 1, level) str1,
regexp_substr(str2, '[^,]+', 1, level)||'=' str2,
level as lvl
from t1
cross join t2
connect by level <= regexp_count(str1, '[^|||]+')
), t4 as
(
select case when lvl = 1 then
replace(str1,'[','['||str2)
else
str2||str1
end as str, lvl
from t3
)
select listagg(str,',') within group (order by lvl) as "Result String" from t4;
Result String
----------------------------------------------------------------------------------------
Region[actingname=Coding Analyst,commonid=BA,insturmentid=04561,commonname=Water Bridge]
P.S. I considered the second one as a sample, and took the 4 first string due to number of substrings seperated by triple-pipes due to the number of tuple labels ending with equality sign is 4.
Demo
this will work:
select substr(regexp_replace(regexp_replace(regexp_replace
(regexp_replace(regexp_replace("col1",'\[','[actingname='),
'\|\|\|',',commonid=',1,1,'i'),
'\|\|\|',',insturmentid=',1,1,'i'),
'\|\|\|',',commonname=',1,1,'i'),
'\|',']',1,1,'i'),
1,regexp_instr(regexp_replace(regexp_replace(regexp_replace
(regexp_replace(regexp_replace("col1",'\[','[actingname='),
'\|\|\|',',commonid=',1,1,'i'),
'\|\|\|',',insturmentid=',1,1,'i'),
'\|\|\|',',commonname=',1,1,'i'),
'\|',']',1,1,'i'),'\]')-1 )||']'
from Table1;
check:
http://sqlfiddle.com/#!4/3ddfa0/11
thanks!!!!!!

Random data sampling with oracle sql, data generation

i need to generate some sample data from a population. I want to do this with an SQL query on an Oracle 11g database.
Here is a simple working example with population size 4 and sample size 2:
with population as (
select 1 as val from dual union all
select 2 from dual union all
select 3 from dual union all
select 4 from dual)
select val from (
select val, dbms_random.value(0,10) AS RANDORDER
from population
order by randorder)
where rownum <= 2
(the oracle sample() funtion didn't work in connection with the WITH-clause for me)
But now I, I want to "upscale" or multiply my sample data. So that I can get something like 150 % sample data of the population data (population size 4 and sample size 6, e.g.)
Is there a good way to achieve this with an SQL query?
You could use CONNECT BY:
with population(val, RANDOMORDER) as (
select level, dbms_random.value(0,10) AS RANDORDER
from dual
connect by level <= 6
ORDER BY RANDORDER
)
select val
FROM population
WHERE rownum <= 4;
db<>fiddle demo
The solution depends, if you want all rows from first initial set(s) and random additional rows from last one then use:
with params(size_, sample_) as (select 4, 6 from dual)
select val
from (
select mod(level - 1, size_) + 1 val, sample_,
case when level <= size_ * floor(sample_ / size_) then 0
else dbms_random.value()
end rand
from params
connect by level <= size_ * ceil(sample_ / size_)
order by rand)
where rownum <= sample_
But if you allow possibility of result like (1, 1, 2, 2, 3, 3), where some values may not appear at all in output (here 4) then use this:
with params(size_, sample_) as (select 4, 6 from dual)
select val
from (
select mod(level - 1, size_) + 1 val, sample_, dbms_random.value() rand
from params
connect by level <= size_ * ceil(sample_ / size_)
order by rand)
where rownum <= sample_
How it works? We build set of (1, 2, 3, 4) as many times as it results from division sample / size. Then we assign random values. In first case I assign 0 to first set(s), so they will be in output for sure, and random values to last set. In second case randoms are assigned to all rows.

Apply order by in comma separated string in oracle

I have one of the column in oracle table which has below value :
select csv_val from my_table where date='09-OCT-18';
output
==================
50,100,25,5000,1000
I want this values to be in ascending order with select query, output would looks like :
output
==================
25,50,100,1000,5000
I tried this link, but looks like it has some restriction on number of digits.
Here, I made you a modified version of the answer you linked to that can handle an arbitrary (hardcoded) number of commas. It's pretty heavy on CTEs. As with most LISTAGG answers, it'll have a 4000-char limit. I also changed your regexp to be able to handle null list entries, based on this answer.
WITH
T (N) AS --TEST DATA
(SELECT '50,100,25,5000,1000' FROM DUAL
UNION
SELECT '25464,89453,15686' FROM DUAL
UNION
SELECT '21561,68547,51612' FROM DUAL
),
nums (x) as -- arbitrary limit of 20, can be changed
(select level from dual connect by level <= 20),
splitstr (N, x, substring) as
(select N, x, regexp_substr(N, '(.*?)(,|$)', 1, x, NULL, 1)
from T
inner join nums on x <= 1 + regexp_count(N, ',')
order by N, x)
select N, listagg(substring, ',') within group (order by to_number(substring)) as sorted_N
from splitstr
group by N
;
Probably it can be improved, but eh...
Based on sample data you posted, relatively simple query would work (you need lines 3 - 7). If data doesn't really look like that, query might need adjustment.
SQL> with my_table (csv_val) as
2 (select '50,100,25,5000,1000' from dual)
3 select listagg(token, ',') within group (order by to_number(token)) result
4 from (select regexp_substr(csv_val, '[^,]+', 1, level) token
5 from my_table
6 connect by level <= regexp_count(csv_val, ',') + 1
7 );
RESULT
-------------------------
25,50,100,1000,5000
SQL>

Using Oracle REGEXP_SUBSTR to extract uppercase data separated by underscores

sample column data:
Failure on table TOLL_USR_TRXN_HISTORY:
Failure on table DOCUMENT_IMAGES:
Error in CREATE_ACC_STATEMENT() [line 16]
I am looking for a way to extract only the uppercase words (table names) separated by underscores. I want the whole table name, the maximum is 3 underscores and the minimum is 1 underscore. I would like to ignore any capital letters that are initcap.
You can just use regexp_substr():
select regexp_substr(str, '[A-Z_]{3,}', 1, 1, 'c')
from (select 'Failure on table TOLL_USR_TRXN_HISTORY' as str from dual) x;
The pattern says to find substrings with capital letters or underscores, at least 3 characters long. The 1, 1 means start from the first position and return the first match. The 'c' makes the search case-sensitive.
You may use such a SQL Select statement for each substituted individual line
( Failure on table TOLL_USR_TRXN_HISTORY in the below case )
from your text :
select regexp_replace(q.word, '[^a-zA-Z0-9_]+', '') as word
from
(
select substr(str,nvl(lag(spc) over (order by lvl),1)+1*sign(lvl-1),
abs(decode(spc,0,length(str),spc)-nvl(lag(spc) over (order by lvl),1))) word,
nvl(lag(spc) over (order by lvl),1) lg
from
(
with tab as
( select 'Failure on table TOLL_USR_TRXN_HISTORY' str from dual )
select instr(str,' ',1,level) spc, str, level lvl
from tab
connect by level <= 10
)
) q
where lg > 0
and upper(regexp_replace(q.word, '[^a-zA-Z0-9_]+', ''))
= regexp_replace(q.word, '[^a-zA-Z0-9_]+', '')
and ( nvl(length(regexp_substr(q.word,'_',1,1)),0)
+ nvl(length(regexp_substr(q.word,'_',1,2)),0)
+ nvl(length(regexp_substr(q.word,'_',1,3)),0)) > 0
and nvl(length(regexp_substr(q.word,'_',1,4)),0) = 0;
Alternate way to get only table name from below error message , the below query will work only if table_name at end in the mentioned way
with t as( select 'Failure on table TOLL_USR_TRXN_HISTORY:' as data from dual)
SELECT RTRIM(substr(data,instr(data,' ',-1)+1),':') from t
New Query for all messages :
select replace (replace ( 'Failure on table TOLL_USR_TRXN_HISTORY:
Failure on table DOCUMENT_IMAGES:' , 'Failure on table', ' ' ),':',' ') from dual

Order versions as numbers

I have a table with file-names and version with subversion of files separated by ..
FNAME, VERSION
A 0.0.10
B 10.12.412
-- For example
create table file_versions as
select chr(mod(level,13)+65) as fname
, decode(mod(level,99),0, '0',
mod(level,10)||'.'||mod(level,500)||'.'||mod(level,14)
)
as version
from dual connect by level < 1001;
I'd like to order files by version, but use versions as numbers
select fname, version from file_versions
order by fname, version
FNAME, VERSION
A 0.0.10
A 0.0.6
...
I'd like don't think about subversion level(there may be one number (0) or more(1.23.14123)). How should I write order by statement ?
I may write something like:
select fname, version from file_versions
order by fname
, to_number(substr(version, 1, instr(version, '.',1,1)-1))
, to_number(substr(version, instr(version, '.',1,1)+1, instr(version, '.',1,2)-instr(version, '.',1,1)-1))
, to_number(substr(version, instr(version, '.',1,2)+1))
But its not so good and will not work if one digit was added to the version string (e.g. 0.0.0.123). Is there a better solution?
You can use regexp_substr():
order by fname,
cast(regexp_substr(version, '[^.]+', 1, 1) as number),
cast(regexp_substr(version, '[^.]+', 1, 2) as number),
cast(regexp_substr(version, '[^.]+', 1, 3) as number)
You may use two regexp first for enhance you group to add 5 zeros to any group. And another one to take last 5 digits from each group. And you get constant length rows and be able to sort it as chars.
with s(txt) as (select '1' from dual
union all
select '1.12' from dual
union all
select '1.12.410' from dual
union all
select rpad('1.12.410',401,'.03') from dual
union all
select rpad('1.12.410',401,'.03')||'.01' from dual
union all
select rpad('1.12.410',401,'.03')||'.02' from dual
)
select txt,regexp_replace(regexp_replace(txt, '(\d+)','00000\1'),'\d+ (\d{5})','\1') from s
order by regexp_replace(regexp_replace(txt, '(\d+)','00000\1'),'\d+(\d{5})','\1')
It will work up to 99999 version or subversion.
More for fun than as a serious suggestion, here's an alternative to parsing the string -- treating the version numbers as inet addresses.
Trickier when you have three levels in your version, but trivial for four levels:
Starting with the idea of:
select a.i::varchar
from (select '192.168.100.128'::inet i union
select '22.168.100.128'::inet) a
order by 1;
i
--------------------
192.168.100.128/32
22.168.100.128/32
(2 rows)
So for three-level versions you can:
with
versions as (
select '1.12.1' v union
select '1.3.100'),
inets as (
select (v||'.0')::inet i
from versions)
select substr(i::varchar,1,length(i::varchar)-5)
from inets
order by i;
substr
---------
1.3.100
1.12.1
(2 rows)
Maybe everyone should have four level versions ...