Oracle Count how much values in varchar - oracle

I'm testing with a query:
SELECT '-1,-2,-4,-6' "Verdunning"
FROM DUAL
Now I need to know how much values are in the varchar: '-1,-2,-4,-6'. I want to have 4 back.
and when it is '-1,-2,-4,-6,-8' i need to get 5 back. How to do this in an oracle select statement?

Fiddle
SELECT LENGTH(Verdunning) - LENGTH(REPLACE(Verdunning, ',', '')) + 1
FROM (SELECT '-1,-2,-4,-6' AS Verdunning FROM DUAL) T

find number of , then add 1 to get count
Select LEN(column) – LEN(REPLACE(column, ',', ''))+1 as comma_separted_values_count
From table

Try this:
SELECT
REGEXP_COUNT ( REGEXP_REPLACE ( '-1,-2,-4,-6', '".*?"' ), ',' ) + 1
FROM
DUAL;

Using regexp_substr.
select count(regexp_substr( '-1,-2,-4,-6','[^,]+', 1, level)) from dual
connect by regexp_substr( '-1,-2,-4,-6', '[^,]+', 1, level) is not null;

Related

How to Find specific values in oracle

i Have some values in my table like this,
column1
50,52,53,54,56,57,58,60,61,63,75 50,53,54 54,75
i want find values containing
53,54
returns
50,52,53,54,56,57,58,60,61,63,75 50,53,54
rows
or 50,52,53
returns
50,52,53,54,56,57,58,60,61,63,75
i tried;
WHERE Regexp_like(DIST_GRP,('53,54]'))
You can search for each number in the column and use AND to combine the filters:
SELECT *
FROM table_name
WHERE ','||column1||',' LIKE '%,54,%'
AND ','||column1||',' LIKE '%,75,%'
or
SELECT *
FROM table_name
WHERE ','||column1||',' LIKE '%,50,%'
AND ','||column1||',' LIKE '%,52,%'
AND ','||column1||',' LIKE '%,53,%'
or you can split the string and count to make sure you have all the matches:
SELECT t.*
FROM table_name t
CROSS APPLY (
SELECT 1
FROM DUAL
WHERE REGEXP_SUBSTR(t.column1, '\d+', 1, LEVEL) IN (50, 52, 53)
CONNECT BY LEVEL <= REGEXP_COUNT(t.column1, '\d+')
HAVING COUNT(DISTINCT REGEXP_SUBSTR(t.column1, '\d+', 1, LEVEL)) = 3
)
or you can create a collection data type and split the string and re-aggregate into that:
CREATE TYPE int_list AS TABLE OF INT;
then
SELECT t.*
FROM table_name t
CROSS APPLY (
SELECT CAST(
COLLECT(
TO_NUMBER(REGEXP_SUBSTR(t.column1, '\d+', 1, LEVEL))
)
AS INT_LIST
) AS list
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(t.column1, '\d+')
) l
WHERE INT_LIST(50,52,53) SUBMULTISET OF l.list;
db<>fiddle here

How to reverse a string in Oracle (11g) SQL without using REVERSE() function

I am trying to reverse a string without using REVERSE function. I came across one example which is something like:
select listagg(letter) within group(order by lvl)
from
(SELECT LEVEL lvl, SUBSTR ('hello', LEVEL*-1, 1) letter
FROM dual
CONNECT BY LEVEL <= length('hello'));
Apart from this approach,is there any other better approach to do this?
If you're trying to avoid the undocumented reverse() function you could use the utl_raw.reverse() function instead, with appropriate conversion too and from RAW:
select utl_i18n.raw_to_char(
utl_raw.reverse(
utl_i18n.string_to_raw('Some string', 'AL32UTF8')), 'AL32UTF8')
from dual;
UTL_I18N.RAW_TO_CHAR(UTL_RAW.REVERSE(UTL_I18N.STRING_TO_RAW('SOMESTRING','AL32UT
--------------------------------------------------------------------------------
gnirts emoS
So that is taking an original value; doing utl_i18n.string_to_raw() on that; then passing that to utl_raw.reverse(); then passing the result of that back through utl_i18n.raw_to_char().
Not entirely sure how that will cope with multibyte characters, or what you'd want to happen to those anyway...
Or a variation from the discussion #RahulTripathi linked to, without the character set handling:
select utl_raw.cast_to_varchar2(utl_raw.reverse(utl_raw.cast_to_raw('Some string')))
from dual;
UTL_RAW.CAST_TO_VARCHAR2(UTL_RAW.REVERSE(UTL_RAW.CAST_TO_RAW('SOMESTRING')))
--------------------------------------------------------------------------------
gnirts emoS
But that thread also notes it only works for single-byte characters.
You could do it like this:
with strings as (select 'hello' str from dual union all
select 'fred' str from dual union all
select 'this is a sentance.' from dual)
select str,
replace(sys_connect_by_path(substr (str, level*-1, 1), '~|'), '~|') rev_str
from strings
where connect_by_isleaf = 1
connect by prior str = str --added because of running against several strings at once
and prior sys_guid() is not null --added because of running against several strings at once
and level <= length(str);
STR REV_STR
------------------- --------------------
fred derf
hello olleh
this is a sentance. .ecnatnes a si siht
N.B. I used a delimiter of ~| simply because that's something unlikely to be part of your string. You need to supply a non-null delimiter to the sys_connect_by_path, hence why I didn't just leave it blank!
SELECT LISTAGG(STR) WITHIN GROUP (ORDER BY RN DESC)
FROM
(
SELECT ROWNUM RN, SUBSTR('ORACLE',ROWNUM,1) STR FROM DUAL
CONNECT BY LEVEL <= LENGTH('ORACLE')
);
You can try using this function:
SQL> ed
Wrote file afiedt.buf
1 with t as (select 'Reverse' as txt from dual)
2 select replace(sys_connect_by_path(ch,'|'),'|') as reversed_string
3 from (
4 select length(txt)-rownum as rn, substr(txt,rownum,1) ch
5 from t
6 connect by rownum <= length(txt)
7 )
8 where connect_by_isleaf = 1
9 connect by rn = prior rn + 1
10* start with rn = 0
SQL> /
Source
select listagg(rev)within group(order by rownum)
from
(select substr('Oracle',level*-1,1)rev from dual
connect by level<=length('Oracle'));

Oracle PL/SQL Tokenize String with empty position

I've a String like this:
AAA,BBB,,DDD
And i would like to tokenize it using comma and retrieve a table like this:
VALUE LEVEL
AAA 1
BBB 2
(null) 3
DDD 4
I need to know the String and in witch position i found it, without missing null String.
I've tried a code like this but i miss the empty position:
SELECT regexp_substr ('AAA,BBB,,DDD', '[^,]+', 1, level), level
FROM dual
CONNECT BY LEVEL <= LENGTH(regexp_replace ('AAA,BBB,,DDD', '[^,]+'));
The output is this:
VALUE LEVEL
AAA 1
BBB 2
DDD 3
Another simple answer is that replacing comma(,) by space with comma(, ) like below
SELECT trim(regexp_substr (replace('AAA,BBB,,DDD',',',', '), '[^,]+', 1, level)), level
FROM dual
CONNECT BY LEVEL <= REGEXP_COUNT (replace('AAA,BBB,,DDD',',',', '), '[^,]+');
this also works http://sqlfiddle.com/#!4/b255d/26
SELECT token, lvl FROM (
SELECT regexp_substr ('AAA,BBB,,DDD', '[^,]*', 1, LEVEL) token, LEVEL lvl,
lag(regexp_substr ('AAA,BBB,,DDD', '[^,]*', 1, LEVEL)) over(order by level) prev_token
FROM dual
CONNECT BY LEVEL <= LENGTH(regexp_replace ('AAA,BBB,,DDD', '[^,]+'))*2
) WHERE prev_token is null;
in oracle 11g and upper you can do something like this query:
with
tab1(pointer,test,split_test) as
(select
1 as pointer,test,substr(test,0,case when instr(test,',',1,1) = 0 then LENGTH(test)
else instr(test,',',1,1)-1 end) split_test from table1
union all
select
pointer + 1 as pointer,test,
substr(test,instr(test,',',1,pointer) + 1,case when instr(test,',',1,pointer + 1) = 0 then LENGTH(test) else
instr(test,',',1,pointer + 1) - instr(test,',',1,pointer) - 1 end) split_test
from tab1 where pointer - 1 < LENGTH(test)-LENGTH(REPLACE(test,',','')))
select split_test as "value",pointer as "level" from tab1;
SQL Fiddle

remove a varchar2 string from the middle of table data values

Data in the file_name field of the generation table should be an assigned number, then _01, _02, or _03, etc. and then .pdf (example 82617_01.pdf).
Somewhere, the program is putting a state name and sometimes a date/time stamp, between the assigned number and the 01, 02, etc. (82617_ALABAMA_01.pdf or 19998_MAINE_07-31-2010_11-05-59_AM.pdf or 5485325_OREGON_01.pdf for example).
We would like to develop a SQL statement to find the bad file names and fix them. In theory it seems rather simple to find file names that include a varchar2 data type and remove it, but putting the statement together is beyond me.
Any help or suggestions appreciated.
Something like:
UPDATE GENERATION
SET FILE_NAME (?)
WHERE FILE_NAME (?...LIKE '%STRING%');?
You can find the problem rows like this:
select *
from Files
where length(FILE_NAME) - length(replace(FILE_NAME, '_', '')) > 1
You can fix them like this:
update Files
set FILE_NAME = SUBSTR(FILE_NAME, 1, instr(FILE_NAME, '_') -1) ||
SUBSTR(FILE_NAME, instr(FILE_NAME, '_', 1, 2))
where length(FILE_NAME) - length(replace(FILE_NAME, '_', '')) > 1
SQL Fiddle Example
You can also use Regexp_replace function:
SQL> with t1(col) as(
2 select '82617_mm_01.pdf' from dual union all
3 select '456546_khkjh_89kjh_67_01.pdf' from dual union all
4 select '19998_MAINE_07-31-2010_11-05-59_AM.pdf' from dual union all
5 select '5485325_OREGON_01.pdf' from dual
6 )
7 select col
8 , regexp_replace(col, '^([0-9]+)_(.*)_(\d{2}\.pdf)$', '\1_\3') res
9 from t1;
COL RES
-------------------------------------- -----------------------------------------
82617_mm_01.pdf 82617_01.pdf
456546_khkjh_89kjh_67_01.pdf 456546_01.pdf
19998_MAINE_07-31-2010_11-05-59_AM.pdf 19998_MAINE_07-31-2010_11-05-59_AM.pdf
5485325_OREGON_01.pdf 5485325_01.pdf
To display good or bad data regexp_like function will come in handy:
SQL> with t1(col) as(
2 select '826170_01.pdf' from dual union all
3 select '456546_01.pdf' from dual union all
4 select '19998_MAINE_07-31-2010_11-05-59_AM.pdf' from dual union all
5 select '5485325_OREGON_01.pdf' from dual
6 )
7 select col bad_data
8 from t1
9 where not regexp_like(col, '^[0-9]+_\d{2}\.pdf$');
BAD_DATA
--------------------------------------
19998_MAINE_07-31-2010_11-05-59_AM.pdf
5485325_OREGON_01.pdf
SQL> with t1(col) as(
2 select '826170_01.pdf' from dual union all
3 select '456546_01.pdf' from dual union all
4 select '19998_MAINE_07-31-2010_11-05-59_AM.pdf' from dual union all
5 select '5485325_OREGON_01.pdf' from dual
6 )
7 select col good_data
8 from t1
9 where regexp_like(col, '^[0-9]+_\d{2}\.pdf$');
GOOD_DATA
--------------------------------------
826170_01.pdf
456546_01.pdf
To that end your update statement might look like this:
update your_table
set col = regexp_replace(col, '^([0-9]+)_(.*)_(\d{2}\.pdf)$', '\1_\3');
--where clause if needed

"Safe" TO_NUMBER()

SELECT TO_NUMBER('*') FROM DUAL
This obviously gives me an exception:
ORA-01722: invalid number
Is there a way to "skip" it and get 0 or NULL instead?
The whole issue: I have NVARCHAR2 field, which contains numbers and not almost ;-) (like *) and I need to select the biggest number from the column.
Yes, I know it is a terrible design, but this is what I need now... :-S
UPD:
For myself I've solved this issue with
COALESCE(TO_NUMBER(REGEXP_SUBSTR(field, '^\d+')), 0)
From Oracle Database 12c Release 2 you could use TO_NUMBER with DEFAULT ... ON CONVERSION ERROR:
SELECT TO_NUMBER('*' DEFAULT 0 ON CONVERSION ERROR) AS "Value"
FROM DUAL;
Or CAST:
SELECT CAST('*' AS NUMBER DEFAULT 0 ON CONVERSION ERROR) AS "Value"
FROM DUAL;
db<>fiddle demo
COALESCE(TO_NUMBER(REGEXP_SUBSTR(field, '^\d+(\.\d+)?')), 0)
will also get numbers with scale > 0 (digits to the right of the decimal point).
I couldn't find anything better than this:
function safe_to_number(p varchar2) return number is
v number;
begin
v := to_number(p);
return v;
exception when others then return 0;
end;
select COALESCE(TO_NUMBER(REGEXP_SUBSTR( field, '^(-|+)?\d+(\.|,)?(\d+)?$')), 0) from dual;
It will convert 123 to 123, but 123a or 12a3 to 0.
Fitting the original question and rather old skool
select a, decode(trim(translate(b,'0123456789.',' ')),null,to_number(b),0) from
(
select '1' a, 'not a number' b from dual
union
select '2' a, '1234' b from dual
)
It's probably a bit messy rolling your own regexp to test for a number, but the code below might work. I think the other solution by Gabe involving a user defined function is more robust since you are using the built in Oracle functionality (and my regexp is probably not 100% correct) but it might be worth a go:
with my_sample_data as (
select '12345' as mynum from dual union all
select '54-3' as mynum from dual union all
select '123.4567' as mynum from dual union all
select '.34567' as mynum from dual union all
select '-0.3462' as mynum from dual union all
select '0.34.62' as mynum from dual union all
select '1243.64' as mynum from dual
)
select
mynum,
case when regexp_like(mynum, '^-?\d+(\.\d+)?$')
then to_number(mynum) end as is_num
from my_sample_data
This will then give the following output:
MYNUM IS_NUM
-------- ----------
12345 12345
54-3
123.4567 123.4567
.34567
-0.3462 -0.3462
0.34.62
1243.64 1243.64
select DECODE(trim(TRANSLATE(replace(replace(A, ' '), ',', '.'), '0123456789.-', ' ')),
null,
DECODE(INSTR(replace(replace(A, ' '), ',', '.'), '.', INSTR(replace(replace(A, ' '), ',', '.'), '.') + 1),
0,
DECODE(INSTR(replace(replace(A, ' '), ',', '.'), '-', 2),
0,
TO_NUMBER(replace(replace(A, ' '), ',', '.'))))) A
from (select '-1.1' A from DUAL union all select '-1-1' A from DUAL union all select ',1' A from DUAL union all select '1..1' A from DUAL) A;
This code excludes such strings as: -1-1, 1..1, 12-2 and so on. And I haven't used regular expressions here.
A combination of previous solutions (from #sOliver and #Mike Meyers) and trying to grab as much numbers as possible by removing the last '$' from REGEXP.
It can be used to filter the actual number from a configuration table, and have a "kind-of" comment next to the number as '12 Days'.
with my_sample_data as (
select '12345' as mynum from dual union all
select '123.4567' as mynum from dual union all
select '-0.3462' as mynum from dual union all
select '.34567' as mynum from dual union all
select '-.1234' as mynum from dual union all
select '**' as mynum from dual union all
select '0.34.62' as mynum from dual union all
select '24Days' as mynum from dual union all
select '42ab' as mynum from dual union all
select '54-3' as mynum from dual
)
SELECT mynum,
COALESCE( TO_NUMBER( REGEXP_SUBSTR( mynum, '^(-|+)?\d*(.|,)?(\d+)?') ) , 0) is_num
FROM my_sample_data;
would give
MYNUM IS_NUM
-------- ----------
12345 12345
123.4567 123.4567
-0.3462 -0.3462
.34567 0.34567
-.1234 -0.1234
** 0
0.34.62 0.34
24Days 24
42ab 42
54-3 54
Best method seems to be the function solution but if you don't have necessary privileges in the environment you are struggling (like me), then you can try this one:
SELECT
CASE
WHEN
INSTR(TRANSLATE('123O0',
' qwertyuıopğüasdfghjklşizxcvbnmöçQWERTYUIOPĞÜASDFGHJKLŞİZXCVBNMÖÇ~*\/(){}&%^#$<>;#€|:_=',
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
),
'X') > 0
THEN 'Y'
ELSE 'N'
END is_nonnumeric
FROM DUAL
By the way: In my case the problem was due to "," and "." :) So take that into consider. Inspired from this one. Also this one seems more concise.
By the way 2: Dear Oracle, can you please create some built-in functions for such small but invaluable needs?

Resources