How can I know which values are numeric in oracle 9i - sql

I have this database which contains a varchar.
I want to know which records holds numeric values. I tried REGEXP_COUNT and other but I'm running on 9i and I think this is for 10g >
How can I achieve this?
I tried:
select to_number( my_column ) from my_table
But it doesn't work, because well not all of them are numeric.
EDIT
Background.
This table contains employee id's, all of which are numeric ( read 1234 or 24523 or 6655 )
The in the initial database load, when the employee id was unknown instead of using something like -1 they entered texts like:
NA, N/A, NONE, UNK, UNKNOW, TEST, EXTERNAL, WITHOUT_ID
Really the main fault is, that column is varchar and not number as it should.
Now, what I try to do, is to get ll the records that are not numeric ( that don't contain an employee id ) but since that db is 9i, I could not use RegExp

I am afraid you'll have to write your own isnumber function, and then use it, something like this (untested) found in this thread, should work.
DECLARE FUNCTION isNumber(p_text IN VARCHAR2) RETURN NUMBER IS
v_dummy NUMBER;
not_number EXCEPTION;
PRAGMA EXCEPTION_INIT(-, not_number);
BEGIN
v_dummy := TO_NUMBER(p_text);
RETURN 1;
EXCEPTION
WHEN not_number THEN RETURN 0;
END is_number;
After that you could use a decode function combined with your isnumber function to get the results you need.

Just another pure SQL workaround:
select my_column
from my_table
where translate(my_column,'x0123456789','x') is null;

Depends on what you count as 'numeric'. Do you allow negative numbers, decimals or just integers, or scientific notation (eg '1e3'). Are leading zeroes allowed ?
If you just want positive integer values, try
where translate(col,' 1234567890','0') is null

Try this
CREATE OR REPLACE PACKAGE value_tests
AS
FUNCTION get_number( pv_value IN VARCHAR2 ) RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY value_tests
AS
FUNCTION get_number( pv_value IN VARCHAR2 ) RETURN NUMBER
IS
converted_number NUMBER;
invalid_number EXCEPTION;
PRAGMA EXCEPTION_INIT( invalid_number, -01722 );
value_error EXCEPTION;
PRAGMA EXCEPTION_INIT( value_error, -06502 );
BEGIN
<<try_conversion>>
BEGIN
converted_number := TO_NUMBER( pv_value );
EXCEPTION
WHEN invalid_number OR value_error
THEN
converted_number := NULL;
END try_conversion;
RETURN converted_number;
END get_number;
END;
/
Running it on this...
select my_column
, value_tests.get_number( my_column ) my_column_num
from ( select 'mydoghas3legs' my_column from dual
union all select '27.5' my_column from dual
union all select '27.50.5' my_column from dual
)
returns
MY_COLUMN MY_COLUMN_NUM
------------- -------------
mydoghas3legs
27.5 27.5
27.50.5

I do not like using exceptions in normal code but this seems to be the best and safest aproach:
CREATE OR REPLACE FUNCTION "IS_NUMBER" (pX in varchar2) return integer is
n number;
begin
n:=to_number(pX);
return 1;
exception
when others then
return 0;
end;

I manage to work around like this:
select my_column
from my_table
where my_column not like '%1%'
and my_column not like '%2%'
and my_column not like '%3%'
and my_column not like '%4%'
and my_column not like '%5%'
and my_column not like '%6%'
and my_column not like '%7%'
and my_column not like '%8%'
and my_column not like '%9%'
and my_column not like '%0%'
Dirty, but it works. ;)

Yet another approach, here's a function I wrote some time ago:
CREATE OR REPLACE function string_is_numeric
(p_string_in in varchar2)
return boolean is
begin
for i in 1..length(p_string_in) loop
if substr(p_string_in, i, 1) not in ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9') then
return false;
end if;
end loop;
return true;
end;
/
As pointed out in pedromarce's answer, you might need to change that from a boolean return to number or varchar2 to better suit your needs.

Here's my version of the routine, similar to that posted by pedromarce. Note that the posted example doesn't compile due to the "-" exception number. This example compiles and works:
create or replace function IsNumber(
a_Text varchar2
) return char is
t_Test number;
begin
begin
t_Test := to_number(a_Text);
return 'Y';
exception when value_error then
return 'N';
end;
end;
Example usage:
select IsNumber('zzz') from dual;
Result: N
select IsNumber('123.45') from dual;
Result: Y

Related

How can I declare constant in PL SQL without writing procedure in "begin"?

I would like to do something like that:
DECLARE MY_DATE CONSTANT DATE := DATE'2022-07-01';
BEGIN null; end;
SELECT MY_DATE FROM dual
or
DECLARE MY_DATE CONSTANT DATE := DATE'2022-07-01';
BEGIN null; end;
SELECT CASE WHEN sysdate > MY_DATE THEN 1 ELSE 0 END FROM dual
But I am getting error. How can I fix it?
NEW:
From the comment I see you're declaring a constant within a package and your aim is to use it in an SQL statement outside of package.
You need a function to return that constant.
NOTE: I wouldn't recommend to store constants in a package and use it in SQL. I'd prefer to store them in a special table because of overhead you'll get when adding or removing constants. And performance issues are to be expected as well because of context switching while using PLSQL constants in SQL statements.
However, answering your question, here's the example for a package having multiple constants:
CREATE OR REPLACE PACKAGE MYCONSTANTS
IS
MY_DATE CONSTANT DATE := DATE'2022-07-01';
MY_CHAR CONSTANT VARCHAR2(10) := 'ABCD';
function get_my_date return date;
function get_my_char return varchar2;
END;
/
create or replace PACKAGE body MYCONSTANTS IS
function get_my_date return date
is
begin
return my_date;
end;
function get_my_char return varchar2
is
begin
return MY_CHAR;
end;
END;
/
And the you can say:
select MYCONSTANTS.get_my_date from dual;
select MYCONSTANTS.get_my_char from dual;
OLD:
As far as I can see you don't need PL/SQL but just SQL.
The PLSQL is what you would have between "begin" and "end". But in your example, you have just null there.
So, all you need is "define"
def my_date = date '2022-07-01';
begin
null;
end;
/ -- you need this to start execution of an pl/sql block
select &my_date from dual; -- be aware you need to use an "&" before var name
If you're on a recent version you can declare a local function in a CTE:
WITH
FUNCTION MY_DATE RETURN DATE IS
BEGIN
RETURN DATE '2022-07-01';
END;
SELECT MY_DATE FROM dual
Or you can use a normal CTE:
WITH cte (MY_DATE) AS (
SELECT DATE '2022-07-01' FROM DUAL
)
SELECT MY_DATE FROM cte
db<>fiddle

Find invalid numbers - Oracle SQL

I have a table:
table1
column_name value
Test1 80933Y355220
Test1 8093363X5220
Test1 809336355220
Test1 80.98
Test1 -80.98
Test2 1234
Test2 abc
Test2 12-23-09
I need to identify all the invalid number values present in the column value
Output should be:
column_name value
Test1 80933Y355220
Test1 8093363X5220
Test2 abc
Test2 12-23-09
Is there a way to get this list?
The common way to solve this is with a user-defined function:
create or replace function is_number (p_str in varchar2) return varchar2 is
n number;
rv varchar2(1);
begin
begin
n := to_number(p_str);
rv := 'Y';
exception
when others then
rv := 'N';
end;
return rv;
end;
/
select column_name, value, is_number(value) as is_number
from table1
where is_number(value) = 'N'
/
In Oracle 12c R2 we acquired a built-in function to do it: validate_conversion() which handles lots of data types. Find out more.
select column_name
, value
from table1
where validate_conversion(value as number) = 0
/
Here is the inevitable demo on db<>fiddle.
Regex solutions are tricky to get right. It's easy to formulate expressions which exclude valid numbers (such as -80.98) or include non-numbers (such as (12-34-56). Even we do get that bit right (as Gordon's solution does) what happens if we want to allow decimal separators or currency symbols? Or scientific notation? That's a messy regex. It's easy to extend a UDF to handle a format mask and validate_conversion() does it natively:
SELECT VALIDATE_CONVERSION('$100,00' AS NUMBER,
'$999D99', 'NLS_NUMERIC_CHARACTERS = '',.''')
FROM DUAL;
I would use regexp_like() with this pattern:
SELECT column_name, value
FROM table1
WHERE NOT REGEXP_LIKE(value, '^[-]?[0-9]*[.]?[0-9]*$')
Just to give an Oracle 12.1 answer, it doesn't have validate_conversion or the on conversion error clause of to_date, but it does support PL/SQL functions in a WITH clause, allowing something like this:
with function is_number(str varchar2) return varchar2
as
n number;
begin
n := str; return 'Y';
exception
when others then return 'N';
end;
select d.column_name
, d.value
from demo d
where is_number(d.value) = 'N'

String Pattern Matching in Sql / Pl/sql

I need to match a string to a pattern to validate the given string.
The given string could be like this 1234/5678.
I should validate the string in such a way that the first four and the last four characters will have to be numbers and they must be seperated by a slash.
How can I do this in SQL or PL/SQL?
I tried different functions such as REGEXP_LIKE, REGEXP_REPLACE,REGEXP_SUBSTR.
Can anyone please help me on this?
If this needs to be done in PL/SQL (e.g. you're validating user input, rather than data in a table), you can create a function to do the validation, e.g.:
DECLARE
v_str VARCHAR2(10);
FUNCTION validate_string (in_str VARCHAR2) RETURN BOOLEAN
IS
BEGIN
RETURN regexp_like(in_str, '\d{4}/\d{4}');
END validate_string;
PROCEDURE validation_output (in_str VARCHAR2)
IS
BEGIN
IF validate_string (in_str => in_str) THEN
dbms_output.put_line(in_str||': validated');
ELSE
dbms_output.put_line(in_str||': not validated');
END IF;
END validation_output;
BEGIN
v_str := '1234/5678';
validation_output (v_str);
v_str := '12/5678';
validation_output (v_str);
v_str := NULL;
validation_output (v_str);
END;
/
1234/5678: validated
12/5678: not validated
: not validated
if you are using oracle you can user regexp_like
https://www.techonthenet.com/oracle/regexp_like.php
if you are using mysql regexp or rlike
https://dev.mysql.com/doc/refman/5.5/en/regexp.html
for sqlserver IsMatch()
https://github.com/zzzprojects/Eval-SQL.NET/wiki/SQL-Server-Regex-%7C-Use-regular-expression-to-search,-replace-and-split-text-in-SQL#sql-regex---ismatch
ORACLE
SELECT * FROM T WHERE COL REGEXP_LIKE REGULAREXP
MYSQL
SELECT * FROM T WHERE COL RLIKE REGULAREXP
SELECT * FROM T WHERE COL REGEXP REGULAREXP
sample table:
SELECT * FROM ns_98;
4321/4567
43/45
43898/4521
4388/4521
43885/45215
4388///4521
SELECT a
FROM ns_98
WHERE REGEXP_LIKE (a,'^[0-9]{4}/{1}[0-9]{4}$');
output:
4321/4567
4388/4521

Encrypting strings in SQL (likely ORACLE PL SQL). Caesar Cipher

I need something like the Caesar Cipher to be used in my string columns for the every value in each column. It should be made something like n+1:
ABcd012Ab -> BCde123Bc
The string characters may be null, may contain sepparators (, - etc.), they may be upper and lower case (it doesnt matter).
Finaly, it should be created as a procedure, and this procedure should be then used inside an UPDATE query.
It shold maybe look something like this:
Create procedure text_change(n varchar(1000))
declare #i char
declare #l varchar(100 char)
begin
For each #l in n
For each #i in #l
loop
#i = ????
end loop;
return #l;
end;
UPDATE name_of_table
SET name_of_column = text_change(column)
Would be very happy for any help!
Why restrict yourself to Caesar Cipher? You could make use of DBMS_CRYPTO package which allows you to use Data Encryption Standard (DES)
Docs
Firstly, get execute permission to this package from DBA.
SQL> GRANT EXECUTE ON DBMS_CRYPTO TO HR;
Grant succeeded.
Then create a function like this.
CREATE OR REPLACE FUNCTION my_encrypt(
p_source VARCHAR2,
p_key VARCHAR2 )
RETURN VARCHAR2
AS
BEGIN
RETURN UTL_RAW.CAST_TO_VARCHAR2 ( DBMS_CRYPTO.encrypt( UTL_RAW.CAST_TO_RAW (p_source),
dbms_crypto.DES_CBC_PKCS5, UTL_RAW.CAST_TO_RAW (p_key) ) );
END;
/
This uses DES_CBC_PKCS5 Block Cipher Suite.
So, when you run a query like this you get encrypted data.
SQL> SELECT my_encrypt('TREASURE UNDER OAK TREE',
2 'The DBMS_CRYPTO package replaces DBMS_OBFUSCATION_TOOLKIT') AS
3 encrypted
4 FROM dual;
ENCRYPTED
----------------------------
┐↨┐┐♣!┐ o)|┐┐┐┐┐┐┐┐
Decrypt function
CREATE OR REPLACE FUNCTION my_decrypt ( p_source VARCHAR2, p_key VARCHAR2 )
RETURN VARCHAR2 AS
BEGIN
RETURN UTL_RAW.CAST_TO_VARCHAR2 ( DBMS_CRYPTO.decrypt( UTL_RAW.CAST_TO_RAW (p_source), dbms_crypto.DES_CBC_PKCS5, UTL_RAW.CAST_TO_RAW (p_key) ) );
END;
/
SQL> SELECT my_decrypt( my_encrypt('TREASURE UNDER OAK TREE',
2 'The DBMS_CRYPTO package replaces DBMS_OBFUSCATION_TOOLKIT') ,
3 'The DBMS_CRYPTO package replaces DBMS_OBFUSCATION_TOOLKIT') AS
4 decrypted
5 FROM dual;
DECRYPTED
---------------------------------
TREASURE UNDER OAK TREE
You could also use it to encrypt and decrypt the columns in the table.
update yourtable set SOMETEXT =
my_encrypt(SOMETEXT,'The DBMS_CRYPTO package replaces DBMS_OBFUSCATION_TOOLKIT');
update yourtable set SOMETEXT =
my_decrypt(SOMETEXT,'The DBMS_CRYPTO package replaces DBMS_OBFUSCATION_TOOLKIT');
If you only want to "encrypt" alphanumerics, you might try the following. You didn't specify what you wanted done with 9, Z, or z so I just took the next ASCII character (:, [, and { respectively):
SELECT mycolumn
, TRANSLATE( mycolumn
, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
, '123456789:BCDEFGHIJKLMNOPQRSTUVWXYZ[bcdefghijklmnopqrstuvwxyz{' )
FROM mytable;
Hope this helps.
EDIT: I'm not sure why I've continued to think about this, but here is a general solution with user-defined function using Oracle's TRANSLATE() function. It doesn't include numbers but I'm sure those would be an easy addition:
CREATE OR REPLACE FUNCTION caesar_cipher
( p_source IN VARCHAR2, p_offset IN PLS_INTEGER )
RETURN VARCHAR2
IS
c_abc CONSTANT VARCHAR2(128) := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
v_offset PLS_INTEGER;
v_target VARCHAR2(32767);
v_transl VARCHAR2(128);
BEGIN
v_offset := MOD( p_offset, LENGTH(c_abc) );
IF (v_offset < 0) THEN
v_offset := v_offset + LENGTH(c_abc);
END IF;
v_transl := SUBSTR(c_abc, v_offset+1) || SUBSTR(c_abc, 1, v_offset);
v_target := TRANSLATE( p_source, c_abc || LOWER(c_abc), v_transl || LOWER(v_transl) );
RETURN v_target;
END;
/
To "decrypt" use a negative value for the offset instead of a positive one; that is, CAESAR_CIPHER('CDE', -2) is the opposite of CAESAR_CIPHER('ABC', 2). It strikes me as more efficient than examining every character of the source string but I've not run a test to be sure.

Oracle arrays "contains" function

I want to store a list of primary keys to other records in the same table. I then want to be able to perform a select that resembles something like:
SELECT * FROM mytable WHERE myarraycol CONTAINS '123'
I've seen that Oracle has an array data type. However it looks like the EXISTS function only verifies if the element exists at the specified index. Is there a way to verify that a given variable is in the array data type column in a single SQL query?
As an alternative to using the array data type I tried storing the PKs as a comma-delimited string like "123,324,543,23432." My query then looked like:
SELECT * FROM mytable WHERE mystringcol LIKE '%123%'
if I wanted all records with the PK '123' in it. The problem with this (among many others) is that if another record has a value "432,9912399,432" this record will show up because of the "123" in the "9912399."
One way I could solve this problem using "LIKE" and string could be to have my where clause be:
WHERE mystringcol LIKE '%,123,% OR mystringcol LIKE '%123, OR mystringcol LIKE '%,123
to test for "123" being in the middle, start or end of the entire string, but that starts to get ugly and I'd rather not do it this way.
Has anyone done something like this before and can point me in the right direction?
In theory Oracle has MEMBER OF function for collection but regarding 3rd normal form you idea looks strange.
SQL> CREATE OR REPLACE PROCEDURE member_of_example AS
2 TYPE nestedTableType IS TABLE OF VARCHAR2(10);
3 myTable1 nestedTableType;
4 result BOOLEAN;
5 BEGIN
6 myTable1 := nestedTableType('F', 'G', 'S');
7 result := 'George' MEMBER OF myTable1;
8 IF result THEN
9 DBMS_OUTPUT.PUT_LINE('''George'' is a member');
10 END IF;
11 END member_of_example;
12 /
An example for function to get the plsql table from comma-separated stuff:
CREATE OR REPLACE function Str2NmbTbl(p_str varchar2) return number_table is
l_col number_table := number_table();
l_pos number;
l_cnt number := 1;
l_num number;
begin
l_pos := instr(p_str, '[', l_cnt);
while l_pos > 0 loop
l_num := to_number(substr(p_str, l_pos + 1, instr(p_str, ']', 1, l_cnt) - l_pos - 1));
l_col.extend;
l_col(l_cnt) := l_num;
l_cnt := l_cnt + 1;
l_pos := instr(p_str, '[', l_pos + 1);
end loop;
return l_col;
end;
/
with s as
(select 1 id, '[11412][21][3131][3333]' str from dual union all
select 2 id, '[64376][553]' str from dual union all
select 3 id, '[5943][74621][19][3333][0]' str from dual union all
select 4 id, '[21593][22321][43][094]' str from dual --union all
)
select id, Str2NmbTbl(str) collctn
from s;