Regex to get 00 if the string has less digits - sql

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

Related

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;

Replace string where ++ exists 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

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;

cross-dbms way to check if string is numeric

Ok, I have this field: code varchar(255). It contains some values used in our export routine like
DB84
DB34
3567
3568
I need to select only auto-generated (fully numeric) fields
WHERE is_numeric(table.code)
is_numeric() checks if code field contains only positive digits.
Can you propose anything that will work both under mysql 5.1 and oracle 10g?
Below are three separate implementations for each of SQL Server, MySQL and Oracle. None use (or can) the same approach, so there doesn't seem to be a cross DBMS way to do it.
For MySQL and Oracle, only the simple integer test is show; for SQL Server, the full numeric test is shown.
For SQL Server:
note that isnumeric('.') returns 1.. but it can not actually be converted to float. Some text like '1e6' cannot be converted to numeric directly, but you can pass through float, then numeric.
;with tmp(x) as (
select 'db01' union all select '1' union all select '1e2' union all
select '1234' union all select '' union all select null union all
select '1.2e4' union all select '1.e10' union all select '0' union all
select '1.2e+4' union all select '1.e-10' union all select '1e--5' union all
select '.' union all select '.123' union all select '1.1.23' union all
select '-.123' union all select '-1.123' union all select '--1' union all
select '---1.1' union all select '+1.123' union all select '++3' union all
select '-+1.123' union all select '1 1' union all select '1e1.3' union all
select '1.234' union all select 'e4' union all select '+.123' union all
select '1-' union all select '-3e-4' union all select '+3e-4' union all
select '+3e+4' union all select '-3.2e+4' union all select '1e1e1' union all
select '-1e-1-1')
select x, isnumeric(x),
case when x not like '%[^0-9]%' and x >'' then convert(int, x) end as SimpleInt,
case
when x is null or x = '' then null -- blanks
when x like '%[^0-9e.+-]%' then null -- non valid char found
when x like 'e%' or x like '%e%[e.]%' then null -- e cannot be first, and cannot be followed by e/.
when x like '%e%_%[+-]%' then null -- nothing must come between e and +/-
when x='.' or x like '%.%.%' then null -- no more than one decimal, and not the decimal alone
when x like '%[^e][+-]%' then null -- no more than one of either +/-, and it must be at the start
when x like '%[+-]%[+-]%' and not x like '%[+-]%e[+-]%' then null
else convert(float,x)
end
from tmp order by 2, 3
For MySQL
create table tmp(x varchar(100));
insert into tmp
select 'db01' union all select '1' union all select '1e2' union all
select '1234' union all select '' union all select null union all
select '1.2e4' union all select '1.e10' union all select '0' union all
select '1.2e+4' union all select '1.e-10' union all select '1e--5' union all
select '.' union all select '.123' union all select '1.1.23' union all
select '-.123' union all select '-1.123' union all select '--1' union all
select '---1.1' union all select '+1.123' union all select '++3' union all
select '-+1.123' union all select '1 1' union all select '1e1.3' union all
select '1.234' union all select 'e4' union all select '+.123' union all
select '1-' union all select '-3e-4' union all select '+3e-4' union all
select '+3e+4' union all select '-3.2e+4' union all select '1e1e1' union all
select '-1e-1-1';
select x,
case when x not regexp('[^0-9]') then x*1 end as SimpleInt
from tmp order by 2
For Oracle
case when REGEXP_LIKE(col, '[^0-9]') then col*1 end