Checking if the value inserted is a Varchar or an Integer - sql

I'm using SQL Developer/Oracle and I need to check in a trigger if the the value inserted on a specific field is an integer or just a "string".
I tried isnumeric(), but this function doesn't exist in Oracle. What's the equivalent in Oracle since I can't find it anywhere?

From your last comment to the #Ben's answer
'1234' should be considered as a string and fail
it seems like you want to do a data type checking of a literal upon insertion and allow to insert only literals of numeric data types. Oracle does an implicit data type conversion when it makes sense. For example, you have a column of number data type in your table. When you try to insert a character literal '123' to that column, the operation will succeed despite the fact that the literal is of character data type(char, simple character literals are of CHAR data type not varchar2 by default), because Oracle takes a look at the data type of a column, then at data type and elements of the character literal and decides 'Yes, it makes sense to convert that character literal to a number.' and does it. As #Ben said , it probably would be better to let your application do the checking whether a value you are trying to insert into a table is of number or character data type.
Having said that, the probably simplest method to do a data type checking and allow to insert only literals or variables of numeric data types would be creating a package with one overloading function, say isnumber(). First version of the function has a formal parameter of varchar2 data type and its overloaded version has formal parameter of number data type. Depending on a data type of actual parameter Oracle will choose appropriate version of the function:
SQL> create or replace package Util1 as
2 function isnumber(p_val in varchar2)
3 return varchar2;
4 function isnumber(p_val in number)
5 return number;
6 end;
7 /
Package created
SQL> create or replace package body Util1 as
2 function isnumber(p_val in varchar2)
3 return varchar2 is
4 begin
5 raise_application_error(-20000, 'Not a numeric data type');
6 end;
7
8 function isnumber(p_val in number)
9 return number is
10 begin
11 return p_val;
12 end;
13 end;
14 /
Package body created
When you call util1.isnumber() function with actual parameter of numeric data type it simply returns it back, and when the function is called with an actual parameter of a character data type exception will be raised.
SQL> create table t1(col number);
Table created
SQL> insert into t1(col) values(util1.isnumber(123));
1 row inserted
SQL> commit;
Commit complete
SQL> insert into t1(col) values(util1.isnumber('123'));
ORA-20000: Not a numeric data type
ORA-06512: at "HR.UTIL1", line 5
SQL> insert into t1(col) values(util1.isnumber('12345f'));
ORA-20000: Not a numeric data type
ORA-06512: at "HR.UTIL1", line 5
Note This approach wont work in a trigger because of implicit data type conversion. In trigger you would have to do the following:
:new.col_name := util1.isnumber(:new.col_name)
As col_name is of number data type, Oracle will always call version of isnumber() function with formal parameter of number data type and insert will succeed even if actual value being inserted is (say) '123'.

I'm using an if. This means if the value inserted is varhchar I will raise an application error, otherwise I will do nothing.
When you insert a character into a numeric field you'll get an "invalid number" exception raised (ORA-01722). This makes your choice easier; don't test to see if the string you're inserting is a character. Capture the raised exception when a user inserts a character into a numeric field and re-raise as an application error (if you really feel you have to).
There's then no need to test at all.
For example:
create table test ( a number );
begin
insert into test values ('a');
exception when INVALID_NUMBER then
raise_application_error(-20000, 'The number wasn''t a number');
end;
/
It's worth noting that you could also test if something's a number in your application code (if it's an application). You wouldn't have to do the round trip to the database then.

Related

How To Modify The Type of the :new Variable Inside a Create Trigger Before Insert in ORACLE/PLSQL

I defined the column YEAR in my table STORE with datatype int. The data is inside a csv. When inserting i pass the year as a string, because it is either a number or 'N/A'. I tried declaring a trigger to correct this
It should do the following:
If the inserted string is 'N/A' Insert NULL. ELSE insert the value of the string.
CREATE TRIGGER checkYear
BEFORE INSERT ON STORE
FOR EACH ROW BEGIN
IF :new.YEAR = 'N/A' THEN
:new.YEAR := NULL;
ELSE
:new.YEAR := CAST(:new.Year AS INT);
END IF;
END;
When inserting values i get a trigger error from checkYear (ORA-04098) inside SQL Developer compiling the trigger gives PLS-00382
The data type of the :new.year variable will always match the data type of the column in the table. You must filter or parse your strings into integers before placing them in the SQL statement.
You just can use a REPLACE() function in order to get rid of the case whenever upcoming year value equals to N/A such as
CREATE OR REPLACE TRIGGER checkYear
BEFORE INSERT ON store
FOR EACH ROW
BEGIN
:new.year := REPLACE(:new.year,'N/A');
END;
/
Otherwise(if it's a year literal which can be convertible to a numeric one such as '2022' ) no need an explicit casting to a numeric value which's already found out implicitly by the DB.
Btw,
you can prefer to adding that to your Insert statement such as
INSERT INTO store(...,year,...) VALUES(...,REPLACE(year,'N/A'),...)
rather than creating a trigger.
or
you can cast directly within the control file such as
...
year CHAR(4000) "REPLACE(:year,'N/A')"
...
if your importing tool is SQL loader.

How can i do an insert into a table of my DB that has one attribute of ADT type? with Oracle Live SQL

I have created a table for seeing how many people could die from COVID-19 in Latin country's for that i created an ADT structure which have two attributes probabilidad_fallecidos that means probability to death and cantidad_infectados that is the quantity of infected per country, The part i'm having problems is when i try to do an insert says ORA-00947: not enough values
I'm very new at this, this is my first try
Below i will let my ADT structure,my function, my table and my try of insert
ADT
CREATE OR REPLACE TYPE infectados AS OBJECT(
cantidad_infectados number,
probabilidad_fallecidos number,
STATIC FUNCTION cantidad_fallecidos(cantidad_infectados number,probabilidad_fallecidos number) RETURN number
);
Function cantidad_fallecidos
CREATE OR REPLACE TYPE BODY infectados IS
STATIC FUNCTION cantidad_fallecidos(cantidad_infectados number,probabilidad_fallecidos number) RETURN number
IS numero1 number(1);
BEGIN
IF cantidad_infectados > probabilidad_fallecidos*cantidad_infectados THEN
RETURN (probabilidad_fallecidos*cantidad_infectados);
ELSE
RAISE_APLICATION_ERROR(-2000,'Error: cantidad_infectados es menor a la probabilidad de fallecidos');
END IF;
END;
END;
Creation of my table
CREATE TABLE Vnzla_infectado(
vnzlaInf_id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY,
num_infectados infectados
);
Try of insert
INSERT INTO Vnzla_infectado
VALUES (infectados(100,0.1,infectados.cantidad_fallecidos(100,0.1)));
You are getting the error ORA-00947: not enough values because you are supplying one value to insert into a table with two columns, and you are not specifying which column you are trying to insert into so Oracle thinks you are inserting into all columns.
Your vnzlaInf_id column may be generated by an IDENTITY, but it looked to the database as if you were attempting to insert a value into that column and nothing into the num_infectados column, hence the error about not enough values.
So the first thing you need to do is to modify the INSERT statement to tell the database which column you want to insert into:
INSERT INTO Vnzla_infectado (num_infectados)
VALUES ...
I wrote "first thing" because there is another problem with your INSERT statement. If you add that column name, you get another error, ORA-02315: incorrect number of arguments for default constructor. This is because your type constructor has two arguments, but you are specifying three. One way to fix it is to get rid of the third argument:
INSERT INTO Vnzla_infectado (num_infectados)
VALUES (infectados(100,0.1));
This INSERT statement runs successfully.
Alternatively, you may want to add another field to your type:
CREATE OR REPLACE TYPE infectados AS OBJECT(
cantidad_infectados number,
probabilidad_fallecidos number,
your_new_field_name_here number,
STATIC FUNCTION cantidad_fallecidos(cantidad_infectados number,probabilidad_fallecidos number) RETURN number
);
If you are going to change the type, you will have to drop the table first and recreate it afterwards. After doing this, your original INSERT statement runs fine.
While I'm here, there are some other problems I noticed with your static function cantidad_fallecidos. Firstly, there is a typo in RAISE_APLICATION_ERROR, it should be RAISE_APPLICATION_ERROR - you're missing one of the Ps. Secondly, the argument -2000 will get rejected by Oracle: it will complain with ORA-21000: error number argument to raise_application_error of -2000 is out of range if you attempt to raise your custom error. I guess you meant to use -20000 for the error number instead. Thirdly, the condition
cantidad_infectados > probabilidad_fallecidos*cantidad_infectados
looks a bit odd to me. Provided cantidad_fallecidos is greater than zero, then it is equivalent to
1 > probabilidad_fallecidos
Also, are you sure you need to use > rather than >=? This leads to some odd behaviour in unusual cases: if cantidad_infectados is zero, your condition will never be true and your custom error will be raised whatever probabilidad_fallecidos is. To me it makes more sense to validate that probabilidad_fallecidos is between 0 and 1.

Substr with more than 4000 characters gets ORA-06502: PL/SQL: numeric or value error

In the below script if I try to substr for 4000 character it works and displays all text in my particular ID with respective DB field ID and Language, if I increase it even 4001 db pops up the error - ora-06502: pl/sql: numeric or value error.
Create or replace function GET_AER_TEXT5(M_AER_ID IN NUMBER,
F_ID IN VARCHAR2,PREF_LANGUAGE IN VARCHAR2)
IS
AERTEXT VARCHAR2(32000);
LANG_PARAM VARCHAR2(2000);
AER_CLOB CLOB;
BEGIN
FOR c1 IN (
select TEXT from AER_TEXT
where FIELD_ID=F_ID and AER_ID=M_AER_ID and LANGUAGE IN(PREF_LANGUAGE)
)
LOOP
IF c1.text IS NOT NULL THEN
AER_CLOB:=AER_CLOB || c1.text;
END IF;
END LOOP;
AERTEXT:=substr(AER_CLOB,1,4000);
RETURN(AERTEXT);
END;
Importance of increasing this to more than 4000 is to pull complete text data. If the DB column contains more than 4K character it doesn’t work.
I'm calling it with:
select AER_ID,GET_AER_TEXT5(AER_ID,at,field_id,'001')
from AER a,AER_TEXT AT
where AT.field_ID=12345 and a.aer_id=at.aer_id;
Can you please advise how to get rid of this issue.
Prior to Oracle 12c Oracle only allows 4000 bytes in a varchar2 value in an SQL context. You are allowed 32k in PL/SQL, so your function is sort of OK as it stands, even with the substrng getting the first 4001 characters; but only if you call it from PL/SQL. When you try to call it from SQL:
select AER_ID,GET_AER_TEXT5(AER_ID,at,field_id,'001') ...
... you're trying to assign a 4001-character value to the implicit SQL column in the return statement, and that is causing the error you are seeing.
You can either change your SAP call to use a PL/SQL context and a bind variable to get the return value, though you'll still be limited to 32k; or change your function to keep value as a CLOB, which makes the function a bit pointless as you can just get the value from the table. I'm not familiar with SAP so I'm not quite sure how you'd end up coding either approach.

Invalid NEW or OLD specification error

I'm trying to create trigger that checks if a phone number if in the (###) ###-#### format, if it this then nothing will happen, if is not then it will be fixed; however if there are more than 10 digits in the number then it will be turned to NULL.
Unfortunately I keep getting the Invalid NEW or OLD specification error in this trigger and I don't know why.
CREATE OR REPLACE TRIGGER phone_correction
BEFORE INSERT OR UPDATE OF vendor_phone
ON vendors
FOR EACH ROW
WHEN (NEW.vendor_phone != REGEXP_LIKE(vendor_phone, '^\(\d{3}\) \d{3}-\d{4}$'))
BEGIN
IF :NEW.vendor_phone != REGEXP_LIKE(vendor_phone, '^\D*(?:\d\D*){10}$')
THEN
:NEW.vendor_phone := null;
DBMS_OUTPUT.PUT_LINE( 'The phone number is bad so setting to null.');
ELSE
:NEW.vendor_phone := REGEXP_LIKE(vendor_phone, '^\(\d{3}\) \d{3}-\d{4}$');
END IF;
END;
There are several errors here; as others have said you need to explicitly use :new. and :old. to reference columns in your trigger, so REGEXP_LIKE(vendor_phone becomes REGEXP_LIKE(:new.vendor_phone.
However, there are some more fundamental errors.
As with the LIKE operator, REGEXP_LIKE() returns a Boolean. Thus, your statement:
IF :NEW.vendor_phone != REGEXP_LIKE(vendor_phone, '^\D*(?:\d\D*){10}$')
is actually IF <string> != <Boolean>, which'll never work.
Using DBMS_OUTPUT in a trigger isn't of any help to you unless you're going to be there to look at whatever logs you're keeping for every row that's been inserted, and then do something to correct whatever issues there are.
Silently removing data is bad practice, if you're going to change something then it's better to raise an error and let the calling code/user decide what to do instead.
If you don't want to let the calling code/user do anything and definitely want to NULL the column if it doesn't conform to a pattern then don't try and insert the data at all.
The ELSE condition in your IF statement is unnecessary, as :new.vendor_phone is already in the correct format.
Personally, I'd completely remove the trigger and add a constraint to check that the format in the column is the one in which you want:
SQL> alter table vendors
2 add constraint chk_vendors_phone
3 check (regexp_like(vendor_phone, '^\(\d{3}\) \d{3}-\d{4}$'));
Then, when trying to insert data it'll be successful if the format is correct and unsuccessful if the format is incorrect:
SQL> insert into vendors (vendor_phone)
2 values ('(123) 123-1234');
1 row created.
SQL> insert into vendors (vendor_phone)
2 values ('(123) 123-124');
insert into vendors (vendor_phone)
*
ERROR at line 1:
ORA-02290: check constraint (CHK_VENDORS_PHONE) violated
SQL>
You can then decide what to do with the phones that have errored. As I've stated above, if you definitely want to NULL the incorrectly formatted phones then only insert data which matches this pattern. If anyone touches the code the check constraint will ensure that the data is still in the correct format.
If you absolutely must use a trigger, then it can be simplified to something like the following:
create or replace trigger phone_correction
before insert or update of vendor_phone
on vendors
for each row
when (not regexp_like(new.vendor_phone, '^\(\d{3}\) \d{3}-\d{4}$'))
begin
:new.vendor_phone := null;
end;
This checks to see (using Boolean logic) whether the result of the REGEXP_LIKE() function is false. If it is, then it NULLs the phone. Here's an example of it working:
SQL> create table vendors (id number, vendor_phone varchar2(100));
Table created.
SQL> create trigger phone_correction
2 before insert or update of vendor_phone
3 on vendors
4 for each row
5 when (not regexp_like(new.vendor_phone, '^\(\d{3}\) \d{3}-\d{4}$'))
6 begin
7 :new.vendor_phone := null;
8 end;
9 /
Trigger created.
SQL> insert into vendors
2 values (1, '(123) 123-1234');
1 row created.
SQL> insert into vendors
2 values (2, '(123) 123-124');
1 row created.
SQL> select * from vendors;
ID VENDOR_PHONE
---------- --------------------
1 (123) 123-1234
2
SQL>
... instead of setting a phone number to null :new.vendor_phone := null; how would you make so it can automatically modify the phone number into the correct format? (###) ###-####
This is actually the example in the documentation for REGEXP_REPLACE(). To make this more extensible, I'd remove all non-numeric characters from the string and then attempt the transformation. In order to remove the non-numeric characters:
regexp_replace(vendor_phone, '[^[:digit:]]')
This means replace everything that's not in the character class [:digit:] with nothing. Then, to transform you can use sub-expressions as described in the documentation:
regexp_replace(regexp_replace(vendor_phone, '[^[:digit:]]')
, '^([[:digit:]]{3})([[:digit:]]{3})([[:digit:]]{4})$'
, '(\1) \2-\3')
This looks for 3 ({3}) digits twice and then 4 digits, splitting them into sub-expressions and then putting them in the correct format. There are many ways to do this, and this may not be the quickest, but it makes your intention most clear.
I would not do this in a trigger, do this when you insert into the table instead. Better, and if this is a client-side application, you should be ensuring that your numbers are in the correct format before you hit the database at all.
You have to specify the :NEW whenever you are using the column names. try this:
CREATE OR REPLACE TRIGGER phone_correction
BEFORE INSERT OR UPDATE OF vendor_phone
ON vendors
FOR EACH ROW
WHEN (NEW.vendor_phone != REGEXP_LIKE(NEW.vendor_phone, '^\(\d{3}\) \d{3}-\d{4}$'))
BEGIN
IF :NEW.vendor_phone != REGEXP_LIKE(:NEW.vendor_phone, '^\D*(?:\d\D*){10}$')
THEN
:NEW.vendor_phone := null;
DBMS_OUTPUT.PUT_LINE( 'The phone number is bad so setting to null.');
ELSE
:NEW.vendor_phone := REGEXP_LIKE(:NEW.vendor_phone, '^\(\d{3}\) \d{3}-\d{4}$');
END IF;
END;
You must specify the keywords NEW and OLD preceded by a colon (:) everywhere you are referring to the columns.
The only exception to this rule is the WHEN clause.
The NEW and OLD keywords, when specified in the WHEN clause, are not
considered bind variables, so are not preceded by a colon (:).
However, you must precede NEW and OLD with a colon in all references
other than the WHEN clause.
So, in your code, you must refer the new values in the conditions as :NEW.
REGEXP_LIKE(vendor_phone
Should be,
REGEXP_LIKE(:NEW.vendor_phone

SQL trigger not working

CREATE TABLE lab7.standings
(
team_name VARCHAR(100) NOT NULL PRIMARY KEY,
wins INTEGER,
losses INTEGER,
winPct NUMERIC,
CHECK(wins > 0),
CHECK(losses >0)
);
CREATE OR REPLACE FUNCTION
calc_winning_percentage()
RETURNS trigger AS $$
BEGIN
New.winPct := New.wins /(New.wins + New.losses);
RETURN NEW;
END;
$$LANGUAGE plpgsql;
CREATE TRIGGER
update_winning_percentage
AFTER INSERT OR UPDATE ON standings
FOR EACH ROW EXECUTE PROCEDURE calc_winning_percentage();
This is accurately updating the wins in my standings table, but doesn't seem to send my new calculated winning percentage.
Try this:
CREATE TRIGGER update_winning_percentage
BEFORE INSERT OR UPDATE ON standings
FOR EACH ROW EXECUTE PROCEDURE calc_winning_percentage();
In addition to changing the trigger to BEFORE like pointed out by #Grijesh:
I notice three things in your table definition:
1.integer vs. numeric
wins and losses are of type integer, but winPct is numeric.
Try the following:
SELECT 1 / 4, 2 / 4
Gives you 0 both times. The result is of type integer, fractional digits are truncated towards zero. This happens in your trigger function before the integer result is coerced to numeric in the assignment. Therefore, changes in wins and losses that only affect fractional digits are lost to the result. Fix this by:
.. either changing the column definition to numeric for all involved columns in the base table.
.. or changing the trigger function:
NEW.winPct := NEW.wins::numeric / (NEW.wins + NEW.losses);
Casting one of the numbers in the calculation to numeric (::numeric) forces the result to be numeric and preserves fractional digits.
I strongly suggest the second variant, since integer is obviously the right type for wins and losses. If your percentage doesn't have to be super-exact, I would also consider using a plain floating point type (real or double precision) for the percentage. Your trigger could then use:
NEW.winPct := NEW.wins::float8 / (NEW.wins + NEW.losses);
2.The unquoted column name winPct
It's cast to lower case and effectively just winpct. Be sure to read about identifiers in PostgreSQL.
3. Schema
Your table obviously lives in a non-standard schema: lab7.standings. Unless that is included in your search_path, the trigger creation has to use a schema-qualified name:
...
BEFORE INSERT OR UPDATE ON lab7.standings
...
P.S.
Goes to show the importance of posting your table definition with this kind of question.