PL/SQL instruction to get numeric column precision - sql

My goal is to create a function that converts a NUMBER variable to a VARCHAR2 while replacing commas for dots as decimal separator. Besides, the string is returned with a mask which depends on the integer and decimal sizes of the number passed as I_qty_value.
declare
L_result VARCHAR2(20);
FUNCTION CONVERT_QTY_FORMAT(I_qty_value IN NUMBER,
I_precision IN NUMBER,
I_scale IN NUMBER)
RETURN VARCHAR2 IS
--
L_conv_value VARCHAR2(255);
L_mask VARCHAR2(50);
L_integer_size NUMBER := I_precision - I_scale;
L_decimal_size NUMBER := I_scale;
--
BEGIN
--
-- Apply mask only if price is a decimal.
--
IF round(I_qty_value) = I_qty_value THEN
--
L_conv_value := TRIM(TO_CHAR(I_qty_value));
--
ELSE
--
-- Mask constructor based on value's length and precision.
--
L_mask := LTRIM(LPAD('0', L_integer_size , 9)) || 'D' || LTRIM(LPAD('0', L_decimal_size, 0));
--
-- Convert number to string using previous mask.
--
L_conv_value := TRIM(REPLACE(TO_CHAR(I_qty_value, L_mask),',','.'));
--
END IF;
--
RETURN L_conv_value;
--
END CONVERT_QTY_FORMAT;
begin
L_result := CONVERT_QTY_FORMAT(1000.999, 6, 2);
dbms_output.put_line(L_result);
end;
Although the function is already working, the two input parameters (I_precision and I_scale) are being manually passed. I would like to create an additional function which would return the variable number precision and scale based on the input variable datatype. Is there any PL/SQL instruction or maybe a core table that may help me doing this?
For example, let's suppose tbl1_1.column_1 is a NUMBER(8,3).
What's the best way to get both 8 and 3 values from column_1?
Thanks in advance!

You can find this information in user_tab_cols and all_tab_cols views:
create table tbl1_1(column_1 number(8,3));
select column_name, data_precision, data_scale
from user_tab_cols
where table_name = 'TBL1_1' and column_name = 'COLUMN_1';
COLUMN_NAME DATA_PRECISION DATA_SCALE
------------------------------ -------------- ----------
COLUMN_1 8 3

Related

Oracle : large Number getting converted scientific notation (40 digits) on concatenation

Oracle converts Number to string on concatenation or moving it to_char.
Example:
select value from tab;
value
9076725748515642018090620180906173606980000000000000000000000000000000000000000
select RPAD(value,LENGTH(value)+1,chr(135)) from tab;
RPAD(value,LENGTH(value)+1,chr(135))
9.0767257485156420180906201809061736E+78‡
Intended result
9076725748515642018090620180906173606980000000000000000000000000000000000000000‡
Maybe PL/SQL can help you. You can try below script.
CREATE OR REPLACE FUNCTION test123 (p_number NUMBER) return VARCHAR2
IS
v_no VARCHAR2(100);
BEGIN
EXECUTE IMMEDIATE
'select '''||p_number||'''||CHR(135) '||
' from dual'
INTO v_no;
RETURN v_no;
END;
/
SELECT TEST123(9076725748515642018090620180906173606980000000000000000000000000000000000000000)
FROM DUAL;
OUTPUT
--------------------------------------------------------------------------------
9076725748515642018090620180906173606980000000000000000000000000000000000000000‡

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.

Ways of handling unknown data type in oracle

I have a statement that executes a sql like this:
execute immediate cursor_rule.rule_sql into rule_result ;
my problem is that the output of rule_sql can be anything from null, to boolean to a number.
How do I define rule_result in a situation like this?
You can use:
DECLARE
rule_result VARCHAR2(4000);
BEGIN
EXECUTE IMMEDIATE :your_sql INTO rule_result;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL; -- Handle what should happen when the SQL returns zero rows.
WHEN TOO_MANY_ROWS THEN
NULL; -- Handle what should happen when the SQL returns two or more rows.
END;
/
If the result of your sql statement is a:
String data type then it gets stored in the rule_result as is.
numeric data type then Oracle will implicitly call TO_CHAR on it to convert it to a VARCHAR2 value exactly long enough to hold its significant digits.
DATE data type then Oracle will implicitly call TO_CHAR( date_value, NLS_DATE_FORMAT ) using the NLS_DATE_FORMAT session parameter as the format model to convert it to a string.
TIMESTAMP data type then Oracle will implicitly call TO_CHAR( timestamp_value, NLS_TIMESTAMP_FORMAT ) using the NLS_TIMESTAMP_FORMAT session parameter as the format model to convert it to a string.
You can parse the SQL statement using DBMS_SQL to discover the column data type. For example:
declare
l_cursor_id pls_integer := dbms_sql.open_cursor;
l_pointless_count pls_integer;
l_desc_cols dbms_sql.desc_tab;
l_sql long := 'select dummy as teststring, 123 as testnum, sysdate as testdate from dual';
begin
dbms_sql.parse(l_cursor_id, l_sql, dbms_sql.native);
dbms_sql.describe_columns(l_cursor_id, l_pointless_count, l_desc_cols);
for i in 1..l_desc_cols.count loop
dbms_output.put_line
( rpad(l_desc_cols(i).col_name,31) || lpad(l_desc_cols(i).col_type,4) );
end loop;
dbms_sql.close_cursor(l_cursor_id);
end;
Output:
TESTSTRING 1
TESTNUM 2
TESTDATE 12
Type codes are defined in DBMS_TYPES and the documentation (which as I discovered last week do not necessarily agree).

Get Maximum Length allowed in column | Oracle

How to get the Max and Min length allowed in column of varchar2.
I have to test for incoming data from the temp table which is coming from some remote db. And each row value is to be tested for specific columns that it has maximum or minimum value which can be set into the column.
So I was to get the column specs using its schema details. I did make a proc for that:
PROCEDURE CHK_COL_LEN(VAL IN VARCHAR2,
MAX_LEN IN NUMBER :=4000,
MIN_LEN IN NUMBER :=0,
LEN_OUT OUT VARCHAR2)
IS
BEGIN
IF LENGTH(VAL)<MIN_LEN THEN
LEN_OUT := 'ERROR';
RETURN;
ELSIF LENGTH(VAL)>MAX_LEN THEN
LEN_OUT := 'ERROR';
RETURN;
ELSE
LEN_OUT := 'SUCCESS';
RETURN;
END IF;
END;
END CHK_COL_LEN;
But the problem is, it is not reusable and is a bit hardcoded. I have to explicitly send MAX and MIN value for each value along with the data to be checked.
So at the proc call, it's something like:
CHK_COL_LEN(EMP_CURSOR.EMP_ID, 5, 1, LEN_ERROR_MSG);
I instead want something like: (If something like this exist!)
CHK_COL_LEN(EMP_CURSOR.EMP_ID,
EMP.COLUMN_NAME%MAX_LENGTH,
EMP.COLUMN_NAME%MIN_LENGTH,
LEN_ERROR_MSG)
Thanks in advance.
EDIT
select max(length(col)) from table;
This is a solution, but again I will have to run this query each time to set the two variables for MAX and MIN value. And running extra two queries for each value and then setting 2 variables will cost be significant lose in performance when in have about 32 tables, each with 5-8 varchar2 columns and average rows of about 40k-50k in each table
You can query the table 'user_tab_columns table' to retrieve metadata information of a specific table:
SELECT
COLUMN_NAME, DATA_LENGTH, DATA_PRECISION
FROM
user_tab_columns
WHERE
t.table_name IN ('<YOURTABLE>');
with this information you can query the metadata directly in your stored procedure:
...
SELECT
CHAR_LENGTH INTO max_length
FROM
user_tab_columns
WHERE
table_name = '<YOURTABLE>' AND COLUMN_NAME = '<YOURCOLUMN>';
...
Exmple Procedure to get max length of table/column:
create or replace PROCEDURE GET_MAX_LENGTH_OF_COLUMN(
tableName IN VARCHAR2,
columnName IN VARCHAR2,
MAX_LENGTH OUT VARCHAR2)
IS
BEGIN
SELECT CHAR_LENGTH INTO MAX_LENGTH
FROM user_tab_columns
WHERE table_name = tableName AND COLUMN_NAME = columnName;
END GET_MAX_LENGTH_OF_COLUMN;
Try creating your procedure like this:
create or replace procedure Checking_size(column_name varchar2,columnvalue varchar2,state out varchar2) is
begin
execute immediate 'declare
z '||column_name||'%type;
begin
z:=:param2;
end;' using columnvalue;
state:='OK';
exception when value_error then
state:='NOT OK';
end;
As you can see i simulate an error assignment. If columnvalue length is bigger than the column i pass as column_name it will throws value_error exception and return NOT OK, else return OK.
For example, if your_table.your_column refer to a column with length (3) then return NOT OK.
declare
state varchar2(10);
begin
Checking_size('your_table.your_column','12345',state);
dbms_output.put_line(state);
end;
If the list of tables is not much you can specify the MIN Value using CHECK Constraint on the table.
Any DML on the table would automatically fail if it exceeds length assigned to that column.
CREATE TABLE suppliers
(
supplier_id numeric(4),
supplier_name varchar2(50),
CONSTRAINT check_supplier_id
CHECK (length(supplier_name) > 5 )
);

Converting small-ish Oracle long raw values to other types

I have an Oracle table that contains a field of LONG RAW type that contains ASCII character data. How can I write a query or view that will convert this to a more easily consumed character string? These are always going to be single-byte characters, FWIW.
Maybe
select ...., to_lob(long_raw) from old_table
(http://www.psoug.org/reference/convert_func.html)
or
UTL_RAW.CAST_TO_VARCHAR2(b)
(http://www.dbasupport.com/forums/showthread.php?t=5342).
I found this quote:
In Oracle9i, you can even:
alter table old_table modify ( c clob
);
to convert it.
See here: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1037232794454
Edit
The max length of a varchar2 column is 4000. Is that too short?
I have found this works well on CLOB data types. I would believe the same would hold true for LOB types.
create or replace function lob2char(clob_col clob) return varchar2 IS
buffer varchar2(4000);
amt BINARY_INTEGER := 4000;
pos INTEGER := 1;
l clob;
bfils bfile;
l_var varchar2(4000):='';
begin
LOOP
if dbms_lob.getlength(clob_col)<=4000 THEN
dbms_lob.read (clob_col, amt, pos, buffer);
l_var := l_var||buffer;
pos:=pos+amt;
ELSE
l_var:= 'Cannot convert. Exceeded varchar2 limit';
exit;
END IF;
END LOOP;
return l_var;
EXCEPTION
WHEN NO_DATA_FOUND THEN
return l_var;
END;
INSERT INTO NEWTABLE (NEWCOLUMN) SELECT RTRIM(lob2char(OLDCOLUMN)) FROM OLDTABLE;