Ora 06512/04088 triggers errors when INSERT INTO statement - regex

I'm at work for a trigger which provide a "domain" for column Molteplicità in a table called Partecipa using a function.
The trigger I've created is the following:
CREATE OR REPLACE TRIGGER dominioMolteplicità
BEFORE INSERT OR UPDATE ON partecipa
FOR EACH ROW
BEGIN
IF moltepl_valido(:NEW.molteplicità) = 'f' THEN
RAISE_APPLICAZION_ERROR(-20002, 'Invalid type');
END IF;
END;
which uses the following function:
CREATE OR REPLACE FUNCTION motepl_valido(mol VARCHAR2) RETURN CHAR IS
BEGIN
IF regexp_like(LOWER(mol), ' [*]\..[*] ') THEN
RETURN 't';
ELSE
RETURN 'f';
END IF;
END;
Table Partecipa has the following columns:
CodP INT,
molteplicità VARCHAR2,
codAss INT,
className VARCHAR2,
PRIMARY KEY (codP),
FOREIGN KEY (className) REFERENCES class(name),
FOREIGN KEY (codAss) REFERENCES associazione(cod)`
and even though in my Associazione table there are rows (in particular codaAss: 42) and in my Class table there are rows (in particular className: 'Impiegato')
When I execute the following statement
insert into Partecipa(molteplicità, className, codAss)
values ('*..*', 'Impiegato', 42);
I get these errors:
ORA-20002 INVALID TYPE
ORA-06512: AT "dominioMolteplicità", line 3
ORA-04088: ERROR DURING EXECUTION OF TRIGGER "dominioMolteplicità"
(Note that if I disable my trigger, the insert statement works properly. There's some problem with the trigger, but I can't find the mistake.)

It's not related to the trigger.
Your function motepl_valido raises ORA-20002 INVALID TYPE if the supplied string (in this case '*..*') does not match the regex ' [*]\..[*] '. It doesn't match because it's missing the required spaces.
Demo showing the effect of a selection of regex patterns (I've added | around the patterns to show the leading and trailing spaces):
with demo (molteplicita) as
( select '*..*' from dual union all
select ' *..* ' from dual union all
select ' *x.* ' from dual )
, patterns (pattern) as
( select '[*]\..[*]' from dual union all
select ' [*]\..[*] ' from dual union all
select ' *[*]\..[*] *' from dual union all
select ' *\*\..\* *' from dual )
select '|'||pattern||'|' as pattern
, '|'||molteplicita||'|' as molteplicita
, case when regexp_like(molteplicita, pattern) then 'Yes' else 'No' end as matched
from demo cross join patterns
order by pattern, molteplicita desc;
PATTERN MOLTEPLICITA MATCHED
---------------- ------------ -------
| *[*]\..[*] *| |*..*| Yes
| *[*]\..[*] *| | *x.* | No
| *[*]\..[*] *| | *..* | Yes
| *\*\..\* *| |*..*| Yes
| *\*\..\* *| | *x.* | No
| *\*\..\* *| | *..* | Yes
| [*]\..[*] | |*..*| No
| [*]\..[*] | | *x.* | No
| [*]\..[*] | | *..* | Yes
|[*]\..[*]| |*..*| Yes
|[*]\..[*]| | *x.* | No
|[*]\..[*]| | *..* | Yes
12 rows selected.

Beacue your pattern doesn't conform your data
I suppose regexp_like( lower(mol), '\*..\*') would be alright, and in this case the values such as '*=-*' or '*34*' for molteplicità would work.
Btw, even using '[\*]..[\*]'(where backslash used as an escape character) as the pattern might be possible for the above regular expression.
Demo :
with t( mol ) as
(
select '*24*' from dual union all
select 'B' from dual union all
select '*=-*' from dual
)
select
case when regexp_like(lower(mol), '\*..\*') then 't' else 'f' end suggested_pattern1,
case when regexp_like(lower(mol), '[\*]..[\*]') then 't' else 'f' end suggested_pattern2,
case when regexp_like(lower(mol), '[*]\..[*]') then 't' else 'f' end original_pattern,
case when regexp_like(lower(mol), '*..*') then 't' else 'f' end anticipated_pattern
from t;
SUGGESTED_PATTERN1 SUGGESTED_PATTERN2 ORIGINAL_PATTERN ANTICIPATED_PATTERN
t t f t
f f f t
t t f t
P.S. Note that anticipated_pattern would fail also ( for mol = 'B' in the above sample).

Related

Oracle SQL - Replace space/alpanumeric/non-alphanumeric with space and replace with word?

Hi I want to replace following examples with the following:
(LTRIM) Replace any space at the beginning before first Alphanumerical character
(RTRIM) Replace any space at the end after last Alphanumerical character
Replace any non-alpha numerical with space
Replace any empty (not null) with word "UNKNOWN"
In case of only space replace all spaces with word "UNKNOWN"
' abc ' -> 'abc'
'abc ' -> 'abc'
' abc ' -> 'abc'
'!ab c ? ' -> 'ab c'
' a b c ' -> 'a b c'
'a!b?c $ ' -> 'a b c'
' ' -> 'UNKNOWN'
'' -> 'UNKNOWN'
null -> null
These are the queries I'm working on but I'm not getting anywhere:
select
'-|&#/,.‘“<>():;' as default1,
translate(SUBSTR(MAX ('-|&#/,.‘“<>():;'),1,70), '-|&#/,.‘“<>():;', ' ') as formatted1
from dual;
select trim(regexp_replace(regexp_replace(' a b cdefgh ' , '[[:space:]]*',''), '(.)', '\1 UNK' )) as formatted from dual;
SELECT LTRIM(RTRIM(' NEXT LEVEL EMPLOYEE ')) from dual;
Replace any empty (not null) with word "UNKNOWN"
This is impossible as, in Oracle, there are no empty strings as an empty string is stored as NULL.
Apart from that impossibility, you can use:
SELECT value,
CASE
WHEN value IS NULL
THEN NULL
ELSE COALESCE(
TRIM(
BOTH ' ' FROM
REGEXP_REPLACE(
value,
'[^A-Za-z0-9]',
' '
)
),
'UNKNOWN'
)
END AS updated_value
FROM table_name;
Which, for the sample data:
CREATE TABLE table_name (value) AS
SELECT ' abc ' FROM DUAL UNION ALL
SELECT 'abc ' FROM DUAL UNION ALL
SELECT ' abc ' FROM DUAL UNION ALL
SELECT '!ab c ? ' FROM DUAL UNION ALL
SELECT ' a b c ' FROM DUAL UNION ALL
SELECT 'a!b?c $ ' FROM DUAL UNION ALL
SELECT ' ' FROM DUAL UNION ALL
SELECT '' FROM DUAL UNION ALL
SELECT null FROM DUAL;
Outputs:
VALUE
UPDATED_VALUE
    abc
abc
abc
abc
  abc
abc
!ab c ?
ab c
 a b c
a b c
a!b?c $
a b c
         
UNKNOWN
null
null
null
null
fiddle

Extracting all lines based on means of two column using Regex on postgresql

I want to extract all lines with meaning of two columns is different or not based on a regex in postgresql.
Example :
col1 | col2 | result
------------------------+--------------+-----------------------
teste 1452-251-99 azert | 1425-251-99 | Same meaning
teste 1225-71-45 | 1225--71.45 | Same meaning
teste 1288-91-75 | 1225--71.45 | Not the same meaning
The format of column col2 must be \d{3,6}-\d{3}-\d{2}, and I consider this column is the correct value
I couldn't find the correct query, and this is my attempt:
update my_table
set result = 'Not the same meaning'
where id in (select t.id from my_table t
where col1 ~'\d{3,6}-\d{3}-\d{2}'
and col1 not like format('%%%s%%', col2)
);
but this returns only where the two columns are not the same
col1 | col2 | result
------------------------+--------------+-----------------------
teste 1452-251-99 azert | 1425-251-99 | Not the same meaning
teste 1225-71-45 | 1225--71.45 | Not the same meaning
teste 1288-91-75 | 1225--71.45 | Not the same meaning
I suggest mingling both columns before coparing them using a regex replace so that only the formated number remains:
update my_table
set result = 'Not the same meaning'
where id in (select t.id from my_table t
where
REGEXP_REPLACE(col1, '^.*\y(\d{3,6})\D+?(\d{2,3})\D+?(\d{2})\y.*$', '\1-\2-\3')
!= REGEXP_REPLACE(col2, '^.*\y(\d{3,6})\D+?(\d{2,3})\D+?(\d{2})\y.*$', '\1-\2-\3')
);
Note: The syntax assumes PG v7.4+.
Here is a db-fiddle that shows it in action.
You need to replace all special chars in between digits in col2 with a - hyphen, and then check using regex if there is a match of this pattern within word boundaries:
where col1 ~'\d{3,6}-\d{3}-\d{2}'
and
col1 !~ CONCAT('\y', REGEXP_REPLACE(col2, '(?<=\d)[^[:space:]0-9]+(?=\d)', '-', 'g'), '\y')
For the 1225--71.45 value in col2, the CONCAT('\y', REGEXP_REPLACE(col2, '(?<=\d)[^[:space:]0-9]+(?=\d)', '-', 'g'), '\y') part will yield \y1225-71-45\y, and it will match 1225-71-45 as a "whole word", when not enclosed with word chars. See the (?<=\d)[^[:space:]0-9]+(?=\d) regex demo here.
Finally, I have found the solution, I extract all number from the two columns, then I check if col2 is substring of col1
select id, col2, col1, res_col2, res_col1 from
(select t.id, col2, col1, regexp_replace(col2, '\D','','g')::numeric::varchar as res_col2,
regexp_replace(col1, '\D','','g')::numeric::varchar as res_col1
from my_table t
inner join fleet_col1 fv on t.col1_id = fv.id
where col1 ~'\d{3,6}-\d{3}-\d{2}' and
NULLIF(regexp_replace(col2, '\D','','g'), '')::numeric::varchar not like NULLIF(regexp_replace(col1, '\D','','g'), '')::numeric::varchar
) tab1
where res_col1 not like '%' || res_col2|| '%';
regexp_replace(col2, '\D','','g')::numeric::varchar ==> Extract digit from string,
Ex 123;:/-456ffrg6787 ===> 1234566787
::numeric::varchar ===> to eliminate the meaningless 0, then cast the result to varchar for comparison.
I make condition where regexp_replace ... in first subquery to eliminate the error invalid input syntax for type numeric: "" if col2/col1 does not contain a number.
where res_col1 not like '%' || res_col2|| '%' check if result of col1 is substring of the first result col2.
Note. I use select instead of update just to see my result

Regular expression - Remove special characters except single white space

From stack overflow, I got the standard reg expression
to eliminate -
a) special characters
b) digits
c) more than 2 spaces to single space
to include -
d) - (hyphen)
e) ' (single quote)
SELECT ID, REGEXP_REPLACE(REGEXP_REPLACE(forenames, '[^A-Za-z-]', ' '),'\s{2,}',' ') , REGEXP_REPLACE(REGEXP_REPLACE(surname, '[^A-Za-z-]', ' '),'\s{2,}',' ') , forenames, surname from table1;
Instead of 2 functions how to get the result in single function?
to include '(single quote) \' is not working in regexp_replace.
Thanks.
Oracle Setup:
CREATE TABLE test_data ( id, value ) AS
SELECT 1, '123a45b£$- ''c45d#{e''' FROM DUAL
Query:
SELECT ID,
REGEXP_REPLACE(
value,
'[^a-zA-Z'' -]| +( )',
'\1'
)
FROM test_data
Output:
ID | REGEXP_REPLACE(VALUE,'[^A-ZA-Z''-]|+()','\1')
-: | :--------------------------------------------
1 | ab- 'cde'
db<>fiddle here

Find out if a string contains only ASCII characters

I need to know whether a string contains only ASCII characters. So far I use this REGEX:
DECLARE
str VARCHAR2(100) := 'xyz';
BEGIN
IF REGEXP_LIKE(str, '^[ -~]+$') THEN
DBMS_OUTPUT.PUT_LINE('Pure ASCII');
END IF;
END;
/
Pure ASCII
' ' and ~ are the first, resp. last character in ASCII.
Problem is, this REGEXP_LIKE fails on certain NLS-Settings:
ALTER SESSION SET NLS_SORT = 'GERMAN';
DECLARE
str VARCHAR2(100) := 'xyz';
BEGIN
IF REGEXP_LIKE(str, '^[ -~]+$') THEN
DBMS_OUTPUT.PUT_LINE('Pure ASCII');
END IF;
END;
/
ORA-12728: invalid range in regular expression
ORA-06512: at line 4
Do anybody knows a solution which works independently from current user NLS-Settings? Is this behavior on purpose or should it be considered as a bug?
You can use TRANSLATE to do this. Basically, translate away all the ASCII printable characters (there aren't that many of them) and see what you have left.
Here is a query that does it:
WITH input ( p_string_to_test) AS (
SELECT 'This this string' FROM DUAL UNION ALL
SELECT 'Test this ' || CHR(7) || ' string too!' FROM DUAL UNION ALL
SELECT 'xxx' FROM DUAL)
SELECT p_string_to_test,
case when translate(p_string_to_test,
chr(0) || q'[ !"#$%&'()*+,-./0123456789:;<=>?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~]',
chr(0)) is null then 'Yes' else 'No' END is_ascii
FROM input;
+-------------------------+----------+
| P_STRING_TO_TEST | IS_ASCII |
+-------------------------+----------+
| This this string | Yes |
| Test this string too! | No |
| xxx | Yes |
+-------------------------+----------+
ASCII function with upper limit of 127 may be used :
declare
str nvarchar2(100) := '\xyz~*-=)(/&%+$#£>|"éß';
a nvarchar2(1);
b number := 0;
begin
for i in 1..length(str)
loop
a := substrc(str,i,1);
b := greatest(ascii(a),b);
end loop;
if b < 128 then
dbms_output.put_line('String is composed of Pure ASCII characters');
else
dbms_output.put_line('String has non-ASCII characters');
end if;
end;
I think I will go for one of these two
IF CONVERT(str, 'US7ASCII') = str THEN
DBMS_OUTPUT.PUT_LINE('Pure ASCII');
END IF;
IF ASCIISTR(REPLACE(str, '\', '/')) = REPLACE(str, '\', '/') THEN
DBMS_OUTPUT.PUT_LINE('Pure ASCII');
END IF;

Regex: How to Implement Negative Lookbehind in PL/SQL

How do I match all the strings that begin with loockup. and end with _id but not prefixed by msg? Here below are some examples:
lookup.asset_id -> should match
lookup.msg_id -> shouldn't match
lookup.whateverelse_id -> should match
I know Oracle does not support negative lookbehind (i.e. (?<!))... so I've tried to explicitly enumerate the possibilities using alternation:
regexp_count('i_asset := lookup.asset_id;', 'lookup\.[^\(]+([^m]|m[^s]|ms[^g])_id') <> 0 then
dbms_output.put_line('match'); -- this matches as expected
end if;
regexp_count('i_msg := lookup.msg_id;', 'lookup\.[^\(]+([^m]|m[^s]|ms[^g])_id') <> 0 then
dbms_output.put_line('match'); -- this shouldn’t match
-- but it does like the previous example... why?
end if;
The second regexp_count expression should't match... but it does like the first one. Am I missing something?
EDIT
In the real use case, I've a string that contains PL/SQL code that might contains more than one lookup.xxx_id instances:
declare
l_source_code varchar2(2048) := '
...
curry := lookup.curry_id(key_val => ''CHF'', key_type => ''asset_iso'');
asset : = lookup.asset_id(key_val => ''UBSN''); -- this is wrong since it does
-- not specify key_type
...
msg := lookup.msg_id(key_val => ''hello''); -- this is fine since msg_id does
-- not require key_type
';
...
end;
I need to determine whether there is at least one wrong lookup, i.e. all occurrences, except lookup.msg_id, must also specify the key_type parameter.
With lookup\.[^\(]+([^m]|m[^s]|ms[^g])_id, you are basically asking to check for a string
starting with lookup. denoted by lookup\.,
followed by at least one character different from ( denoted by [^\(]+,
followed by either -- ( | | )
one character different from m -- [^m], or
two characters: m plus no s -- m[^s], or
three characters: ms and no g -- ms[^g], and
ending in _id denoted by _id.
So, for lookup.msg_id, the first part matches obviously, the second consumes ms, and leaves the g for the first alternative of the third.
This could be fixed by patching up the third part to be always three characters long like lookup\.[^\(]+([^m]..|m[^s.]|ms[^g])_id. This, however, would fail everything, where the part between lookup. and _id is not at least four characters long:
WITH
Input (s, r) AS (
SELECT 'lookup.asset_id', 'should match' FROM DUAL UNION ALL
SELECT 'lookup.msg_id', 'shouldn''t match' FROM DUAL UNION ALL
SELECT 'lookup.whateverelse_id', 'should match' FROM DUAL UNION ALL
SELECT 'lookup.a_id', 'should match' FROM DUAL UNION ALL
SELECT 'lookup.ab_id', 'should match' FROM DUAL UNION ALL
SELECT 'lookup.abc_id', 'should match' FROM DUAL
)
SELECT
r, s, INSTR(s, 'lookup.msg_id') has_msg, REGEXP_COUNT(s , 'lookup\.[^\(]+([^m]..|m[^s]|ms[^g])_id') matched FROM Input
;
| R | S | HAS_MSG | MATCHED |
|-----------------|------------------------|---------|---------|
| should match | lookup.asset_id | 0 | 1 |
| shouldn't match | lookup.msg_id | 1 | 0 |
| should match | lookup.whateverelse_id | 0 | 1 |
| should match | lookup.a_id | 0 | 0 |
| should match | lookup.ab_id | 0 | 0 |
| should match | lookup.abc_id | 0 | 0 |
If you have just to make sure, there is no msg in the position in question, you might want to go for
(INSTR(s, 'lookup.msg_id') = 0) AND REGEXP_COUNT(s, 'lookup\.[^\(]+_id') <> 0
For code clarity REGEXP_INSTR(s, 'lookup\.[^\(]+_id') > 0 might be preferable…
#j3d Just comment if further detail is required.
With the requirements still being kind of vague…
Split the string at the semicolon.
Check each substring s to comply:
WITH Input (s) AS (
SELECT ' curry := lookup.curry_id(key_val => ''CHF'', key_type => ''asset_iso'');' FROM DUAL UNION ALL
SELECT 'curry := lookup.curry_id(key_val => ''CHF'', key_type => ''asset_iso'');' FROM DUAL UNION ALL
SELECT 'asset := lookup.asset_id(key_val => ''UBSN'');' FROM DUAL UNION ALL
SELECT 'msg := lookup.msg_id(key_val => ''hello'');' FROM DUAL
)
SELECT
s
FROM Input
WHERE REGEXP_LIKE(s, '^\s*[a-z]+\s+:=\s+lookup\.msg_id\(key_val => ''[a-zA-Z0-9]+''\);$')
OR
((REGEXP_INSTR(s, '^\s*[a-z]+\s+:=\s+lookup\.msg_id') = 0)
AND (REGEXP_INSTR(s, '[(,]\s*key_type') > 0)
AND (REGEXP_INSTR(s,
'^\s*[a-z]+\s+:=\s+lookup\.[a-z]+_id\(( ?key_[a-z]+ => ''[a-zA-Z_]+?'',?)+\);$') > 0))
;
| S |
|--------------------------------------------------------------------------|
|[tab] curry := lookup.curry_id(key_val => 'CHF', key_type => 'asset_iso');|
| curry := lookup.curry_id(key_val => 'CHF', key_type => 'asset_iso');|
| msg := lookup.msg_id(key_val => 'hello');|
This would tolerate a superfluous comma right before the closing parenthesis. But if the input is syntactically correct, such a comma won't exist.