Using REGEXP_LIKE in Oracle - oracle

In the MyTable table, I have the following data in the MyField column whose size is 80 characters:
MyField
-------
WA
W
W51534
W
W
I am trying to exclude lines starting with WA through regexp_like. But the following query returns the W51534 line and not the W lines :
select MyField
from MyTable
where regexp_like (upper (ltrim (MyField)), '^ [W][^A]');
I would like it to also return lines W. How can I do?
thank you in advance

You may not even need to use REGEXP_LIKE here, regular LIKE may suffice:
SELECT MyField
FROM MyTable
WHERE MyField NOT LIKE 'WA%';
Demo

Finally, I solved my problem, as I said in comment, by adding the rpad command :
regexp_like (upper(rpad(MyField,80,'#')), '^[W][^A]');
If someone has a better idea, I'm interested.
thinks

You can negate the regexp_like, so have it match the patterns you don't want:
with mytable(id, myfield) as (
select 1, 'WA' from dual union all
select 2, 'W' from dual union all
select 3, 'W51534' from dual union all
select 4, 'Z' from dual union all
select 5, '' from dual
)
select id, myfield
from mytable
where not regexp_like(upper(myfield), '^WA') or
myfield is null
order by id;

Related

Oracle ordering with IN clause [duplicate]

Is it possible to keep order from a 'IN' conditional clause?
I found this question on SO but in his example the OP have already a sorted 'IN' clause.
My case is different, 'IN' clause is in random order
Something like this :
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
I would like to retrieve results in (45,2,445,12,789) order. I'm using an Oracle database. Maybe there is an attribute in SQL I can use with the conditional clause to specify to keep order of the clause.
There will be no reliable ordering unless you use an ORDER BY clause ..
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
order by case TestResult.SomeField
when 45 then 1
when 2 then 2
when 445 then 3
...
end
You could split the query into 5 queries union all'd together though ...
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField = 4
union all
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField = 2
union all
...
I'd trust the former method more, and it would probably perform much better.
Decode function comes handy in this case instead of case expressions:
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
ORDER BY DECODE(SomeField, 45,1, 2,2, 445,3, 12,4, 789,5)
Note that value,position pairs (e.g. 445,3) are kept together for readability reasons.
Try this:
SELECT T.SomeField,T.OtherField
FROM TestResult T
JOIN
(
SELECT 1 as Id, 45 as Val FROM dual UNION ALL
SELECT 2, 2 FROM dual UNION ALL
SELECT 3, 445 FROM dual UNION ALL
SELECT 4, 12 FROM dual UNION ALL
SELECT 5, 789 FROM dual
) I
ON T.SomeField = I.Val
ORDER BY I.Id
There is an alternative that uses string functions:
with const as (select ',45,2,445,12,789,' as vals)
select tr.*
from TestResult tr cross join const
where instr(const.vals, ','||cast(tr.somefield as varchar(255))||',') > 0
order by instr(const.vals, ','||cast(tr.somefield as varchar(255))||',')
I offer this because you might find it easier to maintain a string of values rather than an intermediate table.
I was able to do this in my application using (using SQL Server 2016)
select ItemID, iName
from Items
where ItemID in (13,11,12,1)
order by CHARINDEX(' ' + Convert("varchar",ItemID) + ' ',' 13 , 11 , 12 , 1 ')
I used a code-side regex to replace \b (word boundary) with a space. Something like...
var mylist = "13,11,12,1";
var spacedlist = replace(mylist,/\b/," ");
Importantly, because I can in my scenario, I cache the result until the next time the related items are updated, so that the query is only run at item creation/modification, rather than with each item viewing, helping to minimize any performance hit.
Pass the values in via a collection (SYS.ODCINUMBERLIST is an example of a built-in collection) and then order the rows by the collection's order:
SELECT t.SomeField,
t.OtherField
FROM TestResult t
INNER JOIN (
SELECT ROWNUM AS rn,
COLUMN_VALUE AS value
FROM TABLE(SYS.ODCINUMBERLIST(45,2,445,12,789))
) i
ON t.somefield = i.value
ORDER BY rn
Then, for the sample data:
CREATE TABLE TestResult ( somefield, otherfield ) AS
SELECT 2, 'A' FROM DUAL UNION ALL
SELECT 5, 'B' FROM DUAL UNION ALL
SELECT 12, 'C' FROM DUAL UNION ALL
SELECT 37, 'D' FROM DUAL UNION ALL
SELECT 45, 'E' FROM DUAL UNION ALL
SELECT 100, 'F' FROM DUAL UNION ALL
SELECT 445, 'G' FROM DUAL UNION ALL
SELECT 789, 'H' FROM DUAL UNION ALL
SELECT 999, 'I' FROM DUAL;
The output is:
SOMEFIELD
OTHERFIELD
45
E
2
A
445
G
12
C
789
H
fiddle

regex function using only a single quote

I want to select with regex if the column has one single quote only.
Example..
Column1: who's responsible of this
Column2: who''s responsible of this
With this query
Select regexp_substr(column1,'''') from ex_tab
will always consider colum2 that have one single quote but actually it has 2
I want to select only one single quote not the doubles
I cannot use instr function because i might have who's responsible of the's
Um, you can certainly use INSTR to do this:
with str as (select 'who''s in charge?' col1 from dual union all
select 'who''''s in charge?' col1 from dual union all
select 'who''s in charge? I''m in charge!' col1 from dual union all
select 'who is in charge?' col1 from dual union all
select 'who''''s in charge? I''''m in charge!' from dual)
select col1,
case when instr(col1, '''''', 1) != 0 then 'no' else 'yes' end is_ok
from str;
COL1 IS_OK
--------------------------------- -----
who's in charge? yes
who''s in charge? no
who's in charge? I'm in charge! yes
who is in charge? yes
who''s in charge? I''m in charge! no
It'll most likely be faster than the regexp way.
You can negate characters in set using [^].
Some manual. I just noticed that there is no info there that ^ sign as first character in square brackets means "not in this set".
So regexp like:
^('?([^']+'[^']+'?)?)*$
should match everything that do not have two quotes one after another in it. And match single quote. And match strings that have more than one quote, separated by another string.
So, to put it as complete example, what Your want wloud be:
Select
column1,
REGEXP_COUNT(column1,'^(''?([^'']+''[^'']+''?)?)*$')
from
(
select 'who''''s responsible of this' as column1 FROM dual
union
select 'who''s responsible of this' as column1 FROM dual
union
select 'who''s responsible ''of'' this' as column1 FROM dual
union
select '''who''s responsible ''of'' this' as column1 FROM dual
union
select '''' as column1 FROM dual
)
Where is_ok column indicates wether the value contains two quotes, or not.
EDIT:
I just realized that there is much simpler solution. Just check if there are two or more occurances of quotes one after another. So, regexp would be just:
'{2,}
and here is working example, be aware of changing name of column to is_not_ok:
Select
column1,
REGEXP_COUNT(column1,'''{2,}') as is_not_ok
from
(
select 'who''''s responsible of this' as column1 FROM dual
union
select 'who''s responsible of this' as column1 FROM dual
union
select 'who''s responsible ''of'' this' as column1 FROM dual
union
select '''who''s responsible ''of'' this' as column1 FROM dual
union
select '''' as column1 FROM dual
union
select 'this''' as column1 FROM dual
union
select '''''' as column1 FROM dual
)

Get the results sorted in the same order as values inside the in() field of select

Is there is a way to order by the order of the values in an IN() clause?
I have a select query:
Select * from abc where xyz in (a list of values).
I want the result to be sorted in the same order as the list inside the bracket.
One way is that I can put the values in a temp table with an increasing sequence and then join the 2 tables, and then order by the sequence, but this is not a good way.
Is there a way to do this?
No need for a temp table (but not really pretty either)
with list_values (seqnr, id) as (
select 1, 42 from dual
union all
select 2, 43 from dual
union all
select 3, 44 from dual
-- you get the picture
)
select *
from abc
join list_values lv on abc.xyz = lv.id
order by lv.seqnr
One ugly option is to use DECODE:
Select * from abc
WHERE xyz in (a list of values)
ORDER BY DECODE(xyz, 'val1', 1, 'val2', 2, ...)
Thanks for all the answers.
There is another approach, similar to a_horse_with_no_name's approach:
with t as
(select t.*, rownum r from table (sys.odcinumberlist(val1, val2, val3...)) t)
select * from abc ac, t where ac.id = column_value and is_active = 'Y' order by r
This should work too. Which one do you guys think is the best and optimal way to do this?

"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?

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