Replace string where ++ exists SQL - sql

This relates to my previous question
What I now want to do is in my value column, if there are any instance of ++ replace this with 999.
So for e.g. if i have 1+2++3this should be updated to 1+2+999+3
How do i achieve this?
Thanks

Oracle Setup:
CREATE TABLE your_table ( value ) AS
SELECT NULL FROM DUAL UNION ALL
SELECT '1' FROM DUAL UNION ALL
SELECT '1+2' FROM DUAL UNION ALL
SELECT '1++2' FROM DUAL UNION ALL
SELECT '+1' FROM DUAL UNION ALL
SELECT '1+' FROM DUAL UNION ALL
SELECT '1+2++3' FROM DUAL;
Query:
SELECT TRIM( BOTH '+' FROM REPLACE( '+' || value || '+', '++', '+999+' ) )
FROM your_table
Output:
VALUE
---------
999
1
1+2
1+999+2
999+1
1+999
1+2+999+3
If you can have more than one missing element to replace in a row then you need a more complicated solution:
Oracle Setup:
CREATE TABLE your_table ( value ) AS
SELECT NULL FROM DUAL UNION ALL
SELECT '1' FROM DUAL UNION ALL
SELECT '1+2' FROM DUAL UNION ALL
SELECT '1++2' FROM DUAL UNION ALL
SELECT '+1' FROM DUAL UNION ALL
SELECT '1+' FROM DUAL UNION ALL
SELECT '1+2++3' FROM DUAL UNION ALL
SELECT '1+2+++3' FROM DUAL UNION ALL
SELECT '+' FROM DUAL;
Query:
SELECT ( SELECT LISTAGG(
COALESCE(
REGEXP_SUBSTR( t.value, '(.*?)(\+|$)', 1, LEVEL, NULL, 1 ),
'999'
),
'+'
) WITHIN GROUP ( ORDER BY LEVEL )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.value, '\+' ) + 1
) AS value
FROM your_table t
Output:
VALUE
-------------
999
1
1+2
1+999+2
999+1
1+999
1+2+999+3
1+2+999+999+3
999+999

Related

Regex to get 00 if the string has less digits

I have a column that is giving me output like 'ABC2001' , 'ABC100145', 'ABC009282' ,' ABC1901'
I want to change this column value to have '00' in between literals and numbers if number is less than 6 digits. Something like -
COL_A
------------
ABC2001
ABC100145
ABC009282
ABC1901
Expected output
COL_B
------------
ABC002001
ABC100145
ABC009282
ABC001901
How to use regex for this ? Currently I am using
SELECT SUBSTR(COL_A,1,3)||LPAD(REGEXP_REPLACE(COL_A,'\D+'),6,'0') FROM TAB
and it is giving me output like -
ABC210073
ABC210073
You do not need (slow) regular expressions and can use simple string functions:
SELECT col_a,
SUBSTR(col_a, 1, 3) || LPAD(SUBSTR(col_a, 4), 6, '0') AS col_b
FROM table_name;
Which, for your sample data:
CREATE TABLE table_name (col_a) AS
SELECT 'ABC2001' FROM DUAL UNION ALL
SELECT 'ABC100145' FROM DUAL UNION ALL
SELECT 'ABC009282' FROM DUAL UNION ALL
SELECT 'ABC1901' FROM DUAL;
Outputs:
COL_A
COL_B
ABC2001
ABC002001
ABC100145
ABC100145
ABC009282
ABC009282
ABC1901
ABC001901
db<>fiddle here
Just for fun, for different prefixes:
usual string functions: trim/lpad/substr:
with table_name (col_a) AS (
SELECT 'ABC2001' FROM DUAL UNION ALL
SELECT 'ABC100145' FROM DUAL UNION ALL
SELECT 'ABC009282' FROM DUAL UNION ALL
SELECT 'ABC1901' FROM DUAL UNION ALL
-- other different prefixes:
select 'ABC2001' from dual union all
select 'AB100145' from dual union all
select 'A-BC9282' from dual union all
select 'A8C2374' from dual union all
select '7x-ABC32129' from dual union all
select '123ABC8942' from dual
)
select v.*, prefix||num as col_b
from (
select
col_a,
rtrim(col_a,'0123456789') as prefix,
lpad(substr(col_a,1+length(rtrim(col_a,'0123456789'))),6,'0') as num
from table_name
) v
;
DBFiddle
using regex functions:
with table_name (col_a) AS (
SELECT 'ABC2001' FROM DUAL UNION ALL
SELECT 'ABC100145' FROM DUAL UNION ALL
SELECT 'ABC009282' FROM DUAL UNION ALL
SELECT 'ABC1901' FROM DUAL UNION ALL
-- other different prefixes:
select 'ABC2001' from dual union all
select 'AB100145' from dual union all
select 'A-BC9282' from dual union all
select 'A8C2374' from dual union all
select '7x-ABC32129' from dual union all
select '123ABC8942' from dual
)
select
col_a,
regexp_replace(
regexp_replace(col_a,'(\d+)$','00000\1')
,'0*(\d{6})$'
,'\1'
) as col_b
from table_name
;
DBFiddle
regex solution for padding numbers to the maximum their length, ie not knowing max numbers length(if it's not hard-coded 6):
select
v.*,
regexp_replace(
regexp_replace(col_a,'(\d+)$',rpad('0',max_num_length,'0')||'\1')
,'0*(\d{'||max_num_length||'})$'
,'\1'
) as col_b
from (
select t.*, max(length(regexp_substr(col_a,'\d+$')))over() as max_num_length
from table_name t
) v
;
DBFiddle

Oracle SQL order by Varchar2 with '_'

I've got a table with a varchar2 column. Sorting by that column did not give me the expected result:
with test (col) as
(select '_83_' from dual union all
select '_81_' from dual union all
select '4___' from dual union all
select '____' from dual
)
select * from test
order by col desc;
returns:
Col
1. '_83_'
2. '_81_'
3. '4___'
4. '____'
I did expect:
Col
1. '4___'
2. '_83_'
3. '_81_'
4. '____'
Can you explain this and help me to get '4___' to the start of my order by statement?
Edit Using Littlefoots statement for preproduceabillity...
Edit I am using Oracle 12c
Edit NLS_Sort is set to German language. This was the issue.
In my local database, NLS_SORT is set to BINARY so It is not reproducible.
WITH TEMO AS
(
SELECT '_83_' AS X FROM DUAL UNION ALL
SELECT '_81_' AS X FROM DUAL UNION ALL
SELECT '4___' AS X FROM DUAL UNION ALL
SELECT '____' AS X FROM DUAL
)
SELECT * FROM TEMO ORDER BY X DESC;
X
----
____
_83_
_81_
4___
But, after changing NLS_SORT from BINARY to GERMAN, the issue is reproduced.
ALTER SESSION SET NLS_SORT=GERMAN;
WITH TEMO AS
(
SELECT '_83_' AS X FROM DUAL UNION ALL
SELECT '_81_' AS X FROM DUAL UNION ALL
SELECT '4___' AS X FROM DUAL UNION ALL
SELECT '____' AS X FROM DUAL
)
SELECT * FROM TEMO ORDER BY X DESC;
X
----
_83_
_81_
4___
____
You can check the NLS values using the following table:
NLS_SESSION_PARAMETERS
NLS_DATABASE_PARAMETERS
so the conclusion is NLS_SORT parameter must be set accordingly because not everyone wants to sort using technique.
Default value of NLS_SORT is derived from NLS_LANGUAGE.
Refer oracle documents for more information about NLS_SORT.
Solution is to change NLS_SORT according to requirement.
Cheers!!
I don't understand the question. Yes, a comment would be more appropriate but I can't post this:
SQL> with test (col) as
2 (select '_83_' from dual union all
3 select '_81_' from dual union all
4 select '4___' from dual union all
5 select '____' from dual
6 )
7 select * from test
8 order by col;
COL
----
____
4___
_81_
_83_
SQL>
As you can see, my results differ from yours, i.e. I can't reproduce what you are saying. Could you explain it once again, please?
Using Oracle 11g R2:
Select Column1 From (
SELECT CAST( '_83_' AS varchar2(4) ) AS Column1 FROM dual
union all
SELECT CAST( '_81_' AS varchar2(4) ) AS Column1 FROM dual
union all
SELECT CAST( '4___' AS varchar2(4) ) AS Column1 FROM dual
union all
SELECT CAST( '____' AS varchar2(4) ) AS Column1 FROM dual
) A order by Column1 desc
Output:
____
_83_
_81_
4___
Personally I'd recommend Tejash's answer but you can also fudge it with something like:
SELECT * FROM table ORDER BY TRANSLATE(col, '_', 'z') desc;

SQL (Oracle) use two defined list in where-statement

I'm trying to create a query where I use two pre-defines lists with multiple values as filters in a where-stakement. This so I can re-use the query easily for many value-pairs en for many value-pairs.
On Stackoverflow i found:
WITH MyListOfValues(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
SELECT *
FROM DatabaseTable
WHERE Column in (
select col1
from MyListOfValues);
Howerver it fails when i do something like:
WITH MyListOfValues1(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
AND
WITH MyListOfValues2(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
SELECT *
FROM DatabaseTable
WHERE Column1 in (
select col1
from MyListOfValues1)
AND Column2 in (
select col1
from MyListOfValues2);
Does anyone has a solution?:) I'm doing this in a siting corporate enviroment I do not have rights (so far i know) to create my own tables.
Hope you can help me out! All help would be mutch appreciated:-)
You have a syntax error. The correct syntax for CTEs is:
WITH MyListOfValues1(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
),
MyListOfValues2(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
SELECT *
FROM DatabaseTable
WHERE Column1 in (select col1 from MyListOfValues1) AND
Column2 in (select col1 from MyListOfValues2);
I would use exits instead
WITH MyListOfValues1(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
),
MyListOfValues2(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
SELECT *
FROM DatabaseTable dt
WHERE EXISTS (SELECT 1 FROM MyListOfValues1 WHERE col1 = dt.Column1) AND
EXISTS (SELECT 1 FROM MyListOfValues2 WHERE col1 = dt.Column2);
Use a collection and the MEMBER OF operator
SYS.ODCIVARCHAR2LIST is a built in VARRAY you can use:
SELECT *
FROM DatabaseTable
WHERE Column1 MEMBER OF SYS.ODCIVARCHAR2LIST( 'MyValue1', 'MyValue2', 'MyValue3' )
AND Column2 MEMBER OF SYS.ODCIVARCHAR2LIST( 'MyValue1', 'MyValue2', 'MyValue3' );
Or you can define your own collection:
CREATE TYPE StringList IS TABLE OF VARCHAR2(20);
SELECT *
FROM DatabaseTable
WHERE Column1 MEMBER OF StringList( 'MyValue1', 'MyValue2', 'MyValue3' )
AND Column2 MEMBER OF StringList( 'MyValue1', 'MyValue2', 'MyValue3' );
You can even pass the collections as bind variables:
SELECT *
FROM DatabaseTable
WHERE Column1 MEMBER OF :List1
AND Column2 MEMBER OF :List2;

REGEXP_SUBSTR where in condition

I have this data in a table
procedure1/loc1/p1
proc2/loc1/p2/c1
proc1/loc2/p2/c2
procedure3/loc1/p1
procedure4/loc3/p1
I want a query to select specific proc, like proc1 and procedure4 , like wouldnt work so
the ouptput should be like this
proc1/loc2/p2/c2
procedure4/loc3/p1
I tried to use REGEXP_SUBSTR, but how include the list of the procedure that i want ?
--this is wrong
select * from tab1 where REGEXP_SUBSTR(col1,
'[^/]+', 1, 1) in ('proc1','procedure4')
you could check, if the length returned by REGEXP_SUBSTR is greater than 0
select *
from (select 'procedure1/loc1/p1' a from dual union
select 'proc2/loc1/p2/c1' from dual union
select 'proc1/loc2/p2/c2' from dual union
select 'procedure3/loc1/p1' from dual union
select 'procedure4/loc3/p1' from dual) t
where length(regexp_substr(t.a, 'procedure4|proc1')) > 0
alternativly you could use REGEXP_LIKE, which does simply return a boolean and in my oppinion would fit better here.
select *
from (select 'procedure1/loc1/p1' a from dual union
select 'proc2/loc1/p2/c1' from dual union
select 'proc1/loc2/p2/c2' from dual union
select 'procedure3/loc1/p1' from dual union
select 'procedure4/loc3/p1' from dual) t
where regexp_like(t.a, 'procedure4|proc1')
O/P
proc1/loc2/p2/c2
procedure4/loc3/p1
if you would want to get the values from a table you could generate the regex dynamicly with the listagg function provided by oracle. What happens now is, that each possible value, that could occure is getting concatinated with an |, which does represent an or in regex. Due to this you you're not in need for an in, because your regex will have each possible value seperated by an or
select *
from (select 'procedure1/loc1/p1' a from dual union
select 'proc2/loc1/p2/c1' from dual union
select 'proc1/loc2/p2/c2' from dual union
select 'procedure3/loc1/p1' from dual union
select 'procedure4/loc3/p1' from dual) t
where regexp_like(t.a, (select listagg(regexp.b, '|') WITHIN GROUP (ORDER BY regexp.b) regex
from (select 'procedure4' b from dual union
select 'proc1' from dual) regexp))
O/P of the subquery used for the regex would be proc1|procedure4, which would be the regexp needed as seen in the previous example
You don't need to use regular expressions to do that. You just could use ordinary SUBSTR and INSTR to get the substring from the beginning to the first /:
WITH your_table AS (
SELECT 'procedure1/loc1/p1' procedure_name FROM dual
UNION
SELECT 'proc2/loc1/p2/c1' procedure_name FROM dual
UNION
SELECT 'proc1/loc2/p2/c2' procedure_name FROM dual
UNION
SELECT 'procedure3/loc1/p1' procedure_name FROM dual
UNION
SELECT 'procedure4/loc3/p1' procedure_name FROM dual
)
SELECT *
FROM your_table
WHERE SUBSTR(procedure_name,1,INSTR(procedure_name,'/')-1) IN ('proc1','procedure4');
However, if you want to learn to use regular expressions in Oracle. You could use REGEXP_SUBSTR like this:
WITH your_table AS (
SELECT 'procedure1/loc1/p1' procedure_name FROM dual
UNION
SELECT 'proc2/loc1/p2/c1' procedure_name FROM dual
UNION
SELECT 'proc1/loc2/p2/c2' procedure_name FROM dual
UNION
SELECT 'procedure3/loc1/p1' procedure_name FROM dual
UNION
SELECT 'procedure4/loc3/p1' procedure_name FROM dual
)
SELECT *
FROM your_table
WHERE REGEXP_SUBSTR(procedure_name,'^(.+?)/',1,1,'i',1) IN ('proc1','procedure4');
The last parameter tells Oracle to return the match for the pattern between ().
As already mentioned, you also could use REGEXP_LIKE:
WITH your_table AS (
SELECT 'procedure1/loc1/p1' procedure_name FROM dual
UNION
SELECT 'proc2/loc1/p2/c1' procedure_name FROM dual
UNION
SELECT 'proc1/loc2/p2/c2' procedure_name FROM dual
UNION
SELECT 'procedure3/loc1/p1' procedure_name FROM dual
UNION
SELECT 'procedure4/loc3/p1' procedure_name FROM dual
)
SELECT *
FROM your_table
WHERE REGEXP_LIKE(procedure_name,'^(proc1|procedure4)/');
If the procedures you are interesetd in are in a different table, one way could be the following:
Setup:
CREATE TABLE your_table (col) AS
(SELECT 'procedure1/loc1/p1' FROM DUAL UNION ALL
SELECT 'proc2/loc1/p2/c1' FROM DUAL UNION ALL
SELECT 'proc1/loc2/p2/c2' FROM DUAL UNION ALL
SELECT 'procedure3/loc1/p1' FROM DUAL UNION ALL
SELECT 'procedure4/loc3/p1' FROM DUAL
)
CREATE TABLE procedures (name) AS
(SELECT 'proc1' FROM DUAL UNION ALL
SELECT 'procedure4' FROM DUAL
)
You can try this, with no need for regular expressions:
SELECT *
FROM your_table
INNER JOIN procedures
ON (INSTR(col, name) != 0)

How to get query to return rows where first three characters of one row match another row?

Here's my data:
with first_three as
(
select 'AAAA' as code from dual union all
select 'BBBA' as code from dual union all
select 'BBBB' as code from dual union all
select 'BBBC' as code from dual union all
select 'CCCC' as code from dual union all
select 'CCCD' as code from dual union all
select 'FFFF' as code from dual union all
select 'GFFF' as code from dual )
select substr(code,1,3) as r1
from first_three
group by substr(code,1,3)
having count(*) >1
This query returns the characters that meet the cirteria. Now, how do I select from this to get desired results? Or, is there another way?
Desired Results
BBBA
BBBB
BBBC
CCCC
CCCD
WITH code_frequency AS (
SELECT code,
COUNT(1) OVER ( PARTITION BY SUBSTR( code, 1, 3 ) ) AS frequency
FROM table_name
)
SELECT code
FROM code_frequency
WHERE frequency > 1
WITH first_three AS (
...
)
SELECT *
FROM first_three f1
WHERE EXISTS (
SELECT 1 FROM first_three f2
WHERE f1.code != f2.code
AND substr(f1.code, 1, 3) = substr(f2.code, 1, 3)
)
select res from (select res,count(*) over
(partition by substr(res,1,3) order by null) cn from table_name) where cn>1;