PL/SQL Regex search based on both characters and digits

Viewed 209

I have a table TA which has a column inv_ref with has data as below.

inv_ref
----------
MX/3280/20
CT/3281/20
CT/3109/20
MX/3272/20
RF/3275/20

My requirement is to fetch whereas middle 4 digit number of inv_ref between 3270 to 3299 also begin with either MX and CT.

select * from TA where regexp_like(inv_ref, 'CT/32[7-9][0-9]/20')

Above query only returns CT, how can return both CT and MX related values omitting RF?

4 Answers

You can use:

SELECT *
FROM   TA
WHERE  REGEXP_LIKE(inv_ref, '^(CT|MX)/32[7-9][0-9]/20$')

Then if you have the test data:

CREATE TABLE TA ( inv_ref, is_valid ) AS
SELECT 'MX/3280/20', 'Valid' FROM DUAL UNION ALL
SELECT 'CT/3281/20', 'Valid' FROM DUAL UNION ALL
SELECT 'CT/3109/20', 'Invalid, number too low' FROM DUAL UNION ALL
SELECT 'MX/3272/20', 'Valid' FROM DUAL UNION ALL
SELECT 'RF/3275/20', 'Invalid, wrong start' FROM DUAL UNION ALL
SELECT 'CX/3299/20', 'Invalid, wrong start' FROM DUAL UNION ALL
SELECT 'MT/3270/20', 'Invalid, wrong start' FROM DUAL UNION ALL
SELECT 'ACT/3270/20', 'Invalid, wrong start' FROM DUAL;

This outputs:

INV_REF    | IS_VALID
:--------- | :-------
MX/3280/20 | Valid   
CT/3281/20 | Valid   
MX/3272/20 | Valid   

db<>fiddle here

It would be much easier to add virtual generated columns:

alter table your_table add (
  P1 generated always as (regexp_substr(inv_ref,'^[^/]+')),
  P2 generated always as (to_number(regexp_substr(inv_ref,'/(\d+)/',1,1,null,1))),
  P3 generated always as (regexp_substr(inv_ref,'[^/]+$'))
);

In this case you can use standard predicates on those columns. Moreover, you can even create indexes on those columns.

Full test case:

CREATE TABLE YOUR_TABLE ( inv_ref ) AS
SELECT 'MX/3280/20' FROM DUAL UNION ALL
SELECT 'CT/3281/20' FROM DUAL UNION ALL
SELECT 'CT/3109/20' FROM DUAL UNION ALL
SELECT 'MX/3272/20' FROM DUAL UNION ALL
SELECT 'RF/3275/20' FROM DUAL UNION ALL
SELECT 'CX/3299/20' FROM DUAL UNION ALL
SELECT 'MT/3270/20' FROM DUAL UNION ALL
SELECT 'ACT/3270/20' FROM DUAL;

alter table your_table add (
  P1 generated always as (regexp_substr(inv_ref,'^[^/]+')),
  P2 generated always as (to_number(regexp_substr(inv_ref,'/(\d+)/',1,1,null,1))),
  P3 generated always as (regexp_substr(inv_ref,'[^/]+$'))
);

select * from your_table
where p1 IN ('CT', 'MX')
  and p2 BETWEEN 3270 and 3299;

Result:

MX/3280/20  MX        3280 20
CT/3281/20  CT        3281 20
MX/3272/20  MX        3272 20

You could use

SQL> create table my_test ( inv_ref varchar2(100) ) ;

SQL> insert into my_test values ( 'MX/3280/20') ;

SQL> insert into my_test values ( 'CD/3281/20') ;

SQL> insert into my_test values ( 'CD/3109/20') ;

SQL> insert into my_test values ( 'MX/3272/20') ;

SQL> insert into my_test values ( 'RF/3275/20') ;

Table created.

SQL> SQL> SQL>
1 row created.

SQL> SQL>
1 row created.

SQL> SQL>
1 row created.

SQL> SQL>
1 row created.

SQL> SQL>

1 row created.

SQL> commit ;

Commit complete.

Two options ( in my case I use CD instead of CT ). This option will work as long as the strings are limited to the example. If you would have other combination it won't , as CD|MX means C or D or M or X ). See comments to the answer. @MTO, Thanks for your comments.

SQL> select * from my_test where regexp_like(inv_ref, '[CD|MX]/32[7-9][0-9]/20')

INV_REF
--------------------------------------------------------------------------------
MX/3280/20
CD/3281/20
MX/3272/20


SQL> select * from my_test where regexp_like(inv_ref, 'CD/32[7-9][0-9]/20') or 
regexp_like(inv_ref, 'MX/32[7-9][0-9]/20')

INV_REF
--------------------------------------------------------------------------------
MX/3280/20
CD/3281/20
MX/3272/20

You can use the values separated by | (or) as follows:

select * from TA where regexp_like(inv_ref, '^(CT|MX)/32[7-9][0-9]/20');

db<>fiddle

Related