Oracle/Hibernate Like: Finding String that only contain a single ';' - oracle

I'm trying to find all the records that contains only a single ; in a column.
For example
a;sdasd
as;dasd;dasd
as;dasd;das
only a;sdasd will be returned.
I have tried %;% but it will return all the strings that contain ;.

SELECT id
FROM your_table
WHERE LENGTH(col) - LENGTH(REPLACE(col,';')) = 1;
Is one solution.
Another uses REGEXP_LIKE:
WITH q AS (SELECT 1 ID, 'a;b;c;' str FROM dual
UNION
SELECT 2, ';abc' FROM dual
UNION
SELECT 3, 'a;b;c;defg;h' FROM dual
UNION
SELECT 4, 'abcdefghi;' FROM dual
UNION
SELECT 5, 'ab;cde' FROM dual
UNION
SELECT 6, 'abcdef' FROM dual)
SELECT *
FROM q
WHERE regexp_like(str,'^[^;]*;[^;]*$');
ID STR
---------- ------------
2 ;abc
4 abcdefghi;
5 ab;cde

Ine other possibility (no String manipulation is necessary):
FROM your_table
WHERE col like '%;%' AND INSTR(col, ';', 1) = INSTR(col, ';', -1)

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

REGEXP_SUBSTR not able to process only current row

(SELECT LISTAGG(EVENT_DESC, ',') WITHIN GROUP (ORDER BY EVENT_DESC) FROM EVENT_REF WHERE EVENT_ID IN
( SELECT REGEXP_SUBSTR(AFTER_VALUE,'[^,]+', 1, level) FROM DUAL
CONNECT BY REGEXP_SUBSTR(AFTER_VALUE, '[^,]+', 1, level) IS NOT NULL
)
)
A table from which I am fetching AFTER_VALUE has values of integer which is comma seperated like
AFTER_VALUE data
Expected output
1
Event1
1,2
Event1,Event2
1,12,2,5
Event1,Event12,Event2,Event5
15,13
Event15,Event13
these are Ids in EVENT_REF table which have some description. I am trying to basically present
ex. 1,2 as Event1, Event2 and send back from query. There are multiple events so using REPLACE would be very tedious.
When using above query I'm getting error as “ORA-01722: invalid number” whenever there is more than one value in AFTER_VALUE column Ex. if there exists only one id , then the query works but for values like 1,2 or 1,13 etc it throws invalid number error.
PS: The event names are not Event1,Event2 etc , I have just put for reference.
You don't even need regular expressions for this assignment. Standard string function replace() can do the same thing, and faster. You only need an extra 'Event' at the beginning of the string, since that one doesn't "replace" anything.
Like this: (note that you don't need the with clause; I included it only for quick testing)
with
event_ref (after_value) as (
select '1' from dual union all
select '1,2' from dual union all
select '1,12,2,5' from dual union all
select '15,13' from dual
)
select after_value,
'Event' || replace(after_value, ',', ',Event') as desired_output
from event_ref
;
AFTER_VALUE DESIRED_OUTPUT
----------- -----------------------------
1 Event1
1,2 Event1,Event2
1,12,2,5 Event1,Event12,Event2,Event5
15,13 Event15,Event13
Ah,ok, looks, like you have other characters in your comma-separated list, so you can use this query:
with EVENT_REF(EVENT_ID,EVENT_DESC) as (
select 1, 'Desc 1' from dual union all
select 2, 'Desc 2' from dual union all
select 3, 'Desc 3' from dual union all
select 4, 'Desc 4' from dual union all
select 5, 'Desc 5' from dual union all
select 12, 'Desc12' from dual union all
select 13, 'Desc13' from dual union all
select 15, 'Desc15' from dual
)
select
(SELECT LISTAGG(EVENT_DESC, ',')
WITHIN GROUP (ORDER BY EVENT_DESC)
FROM EVENT_REF
WHERE EVENT_ID IN
( SELECT to_number(REGEXP_SUBSTR(AFTER_VALUE,'\d+', 1, level))
FROM DUAL
CONNECT BY level<=REGEXP_COUNT(AFTER_VALUE, '\d+')
)
)
from (
select '1' AFTER_VALUE from dual union all
select '1,2' AFTER_VALUE from dual union all
select '1,12,2,5' AFTER_VALUE from dual union all
select '15,13' AFTER_VALUE from dual
);
PS. And do not forget that to_number has 'default on conversion error' now: https://docs.oracle.com/en/database/oracle/oracle-database/12.2/sqlrf/TO_NUMBER.html
There is no need to split and concatenate substrings, just use regexp_replace:
with EVENT_REF (AFTER_VALUE) as (
select '1' from dual union all
select '1,2' from dual union all
select '1,12,2,5' from dual union all
select '15,13' from dual
)
select regexp_replace(AFTER_VALUE,'(\d+)','Event\1') from EVENT_REF;
REGEXP_REPLACE(AFTER_VALUE,'(\D+)','EVENT\1')
-----------------------------------------------
Event1
Event1,Event2
Event1,Event12,Event2,Event5
Event15,Event13

How to use replace command for multiple characters in PL/SQL

I'm writing a procedure that have to replace a set of special characters with another set of them that are accepted to an application system. How can I re-write better the following statement that I'm using in the procedure?
select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace('%BICI* "(MOTO), |X PLAY? 4G: RED&WHITE& \/<DIRETTA>','(','-'),'%','perc'),'?','.'),'|','-'),':',';'),',','.'),'<','-'),'>','-'),'&','and'),'\','-'),'/','-'),'"','-'),')','-'),'*','-')
from dual;
I can't use a recursive procedure.
Any suggestions?
Thanks!
Ilaria
This is an interesting one. Here's a working example based on Florin's example:
with trans_tbl(id, symbol, txt) as (
select 1, '(', '-' from dual union
select 2, '%', 'perc' from dual union
select 3, '?', '.' from dual union
select 4, '|', '-' from dual union
select 5, ':', ';' from dual union
select 6, ',', '.' from dual union
select 7, '<', '-' from dual union
select 8, '>', '-' from dual union
select 9, '&', 'and' from dual union
select 10, '\', '-' from dual union
select 11, '/', '-' from dual union
select 12, '"', '-' from dual union
select 13, ')', '-' from dual union
select 14, '*', '-' from dual
),
data_tbl(str) as (
select '%BICI* "(MOTO), |X PLAY? 4G: RED&WHITE& \/<DIRETTA>' from dual
),
working_tbl(str, id) as (
SELECT str, 0 id
FROM data_tbl
UNION ALL
SELECT replace(working_tbl.str,symbol,txt), trans_tbl.id
FROM working_tbl
JOIN trans_tbl
ON working_tbl.id = trans_tbl.id - 1
)
--select str, id from working_tbl;
SELECT str
from working_tbl
where id = (select max(id)
from trans_tbl);
STR
----------------------------------------------------------
percBICI- --MOTO-. -X PLAY. 4G; REDandWHITEand ---DIRETTA-

Oracle: elegant way to parse a number to 9,99 format

With Oracle 11g I want to parse a number to strip decimals if their value is 0 and keep two decimal figure after the decimal separator ',' if the value of decimals is different from 0
Example:
1,00 -> 1
1,001 -> 1
0,203 -> 0,20
And so on.
I've obtained something like that in a very unelegant way
select replace(trim(to_char (trunc ('0,2345',2),'9999999990.99')), '.', ',')
from dual
Do you know more elegant way? The output should be a char (not number).
Not sure it's much more elegant, but assuming your replace is to deal with different locales, this might work for you:
with t as (
select 1.00 as n from dual
union all select 1.001 from dual
union all select 0.203 from dual
union all select 0.2345 from dual
union all select 112.999 from dual
)
select n, regexp_replace(to_char(trunc(n, 2), '9999999990D00',
'NLS_NUMERIC_CHARACTERS='',.'''), '[,.]00$', null) as new_n
from t;
N NEW_N
---------- --------------
1 1
1.001 1
0.203 0,20
0.2345 0,23
112.999 112,99
The nls_param argument to to_char let's you dictate whether it used a comma or a period as the decimal separator. If you can set that at session level then the query looks a bit simpler. The regexp_replace strips ,00 (or .00, which come to think of it is overkill) from the end of th string.
As ThinkJet noted the regexp_replace is a bit excessive, and since the decimal seperator is defined in the column clase (and the format has no group separators anyway) it can be done with a plan replace:
with t as (
select 1.00 as n from dual
union all select 1.001 from dual
union all select 0.203 from dual
union all select 0.2345 from dual
union all select 112.999 from dual
union all select 13.08 from dual
)
select n, replace(trim(
to_char(trunc(n, 2), '9999999990D00', 'NLS_NUMERIC_CHARACTERS='',.''')),
',00', null) as new_n
from t;
N NEW_N
---------- --------------
1 1
1.001 1
0.203 0,20
0.2345 0,23
112.999 112,99
13.08 13,08
Still not sure this can be described as 'elegant' though.
To achieve correct results you must deal with numbers, not strings:
with t as (
select 1.00 as n from dual
union all select 1.001 from dual
union all select 0.203 from dual
union all select 0.2345 from dual
union all select 112.999 from dual
union all select 112.105 from dual
union all select 0 from dual
union all select -12.307 from dual
)
select
n,
decode( trunc(n,2) - trunc(n) ,
0, to_char(trunc(n), 'TM9', 'NLS_NUMERIC_CHARACTERS = '', '''),
to_char(trunc(n,2),'9999999990D00', 'NLS_NUMERIC_CHARACTERS = '', ''')
)
string_val
from t
SQLFiddle
P.S. Updated to get incorrect truncation instead of round, as in OP request.
Another variant
SELECT n,
to_char( trunc( n, 2 ),
case when mod( trunc( n, 2 ), 1 ) = 0
then '9990'
else '9990D00'
end, 'NLS_NUMERIC_CHARACTERS='',.''' ) val
from t
;
SQLFiddle demo

ORACLE SQL | Modifying data in ORDER BY

following structure in a ORACLE table:
FILE_NAME
-----------
12345_l.tif
12345_m.tif
12345_r.tif
12345_x.tif
12345_y.tif
Need the following result:
First *_m*
Then *_l*
Then *_r*
Then * (everything else)
Trying with:
SELECT FILE_NAME FROM TABLE
WHERE FILE_NAME LIKE '12345%'
ORDER BY regexp_replace(FILE_NAME, '_m', '_1'),
regexp_replace(FILE_NAME, '_l', '_2'),
regexp_replace(FILE_NAME, '_r', '_3')
But this gives me a wrong result.
Anybody with a hint?
TIA Matt
Change your ORDER BY to order it by a numeric:
ORDER BY regexp_replace(FILE_NAME, '_m', 1),
regexp_replace(FILE_NAME, '_l', 2),
regexp_replace(FILE_NAME, '_r', 3);
e.g.
WITH t
AS (SELECT '12345_l.tif' AS file_name FROM dual
UNION
SELECT '12345_m.tif' FROM dual
UNION
SELECT '12345_r.tif' FROM dual
UNION
SELECT '12345_x.tif' FROM dual
UNION
SELECT '12345_y.tif' FROM dual)
SELECT file_name
FROM t
ORDER BY regexp_replace(FILE_NAME, '_m', 1),
regexp_replace(FILE_NAME, '_l', 2),
regexp_replace(FILE_NAME, '_r', 3);
Gives:
==============
12345_m.tif
12345_l.tif
12345_r.tif
12345_x.tif
12345_y.tif
Hope it helps...
Alternatively you could use:
ORDER BY (CASE SUBSTR(file_name, INSTR(file_name, '_')+1, 1)
WHEN 'm' THEN 1
WHEN 'l' THEN 2
WHEN 'r' THEN 3
ELSE 4
END) ASC;
E.G.:
WITH t
AS (SELECT '12345_l.tif' AS file_name FROM dual
UNION
SELECT '12345_y.tif' FROM dual
UNION
SELECT '12345_r.tif' FROM dual
UNION
SELECT '12345_x.tif' FROM dual
UNION
SELECT '12345_m.tif' FROM dual)
SELECT file_name
FROM t
ORDER BY (CASE SUBSTR(file_name, INSTR(file_name, '_')+1, 1)
WHEN 'm' THEN 1
WHEN 'l' THEN 2
WHEN 'r' THEN 3
ELSE 4
END) ASC;
Gives:
12345_m.tif
12345_l.tif
12345_r.tif
12345_x.tif
12345_y.tif

Resources