Get Maximum Length allowed in column | Oracle - sql

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 )
);

Related

PL SQL Oracle Store Dynamic SQL result (Dynamic in a loop)

I'm new to PL/SQL on Oracle (have to do some work on this but it's clearly not my domain).
So I have to do an 'audit' of our DB which consists in giving for each column of each table of our schema its max length (how we declared it (VARCHAR2(15) for example) and the max value currently of this column (ID 15000 for example) (might evolve and want more data in my results but at the moment i just need this).
I will explain my problem with an example
to be clear :
I have a table EMPLOYEE with 3 columns :
NAME in VARCHAR2(50), the longest i have (in length) is 48
CITY in VARCHAR2(100), the longest i have (in length) is 95
AGE in NUMBER, the longest i have (in length) is 2
So for this table of my schema I would like to have as output of my script (to work on it in excel), it must be taken into account that here the employee table is only one among many others which is returned by the first request:
TABLE_NAME
COLUMN_NAME
MAX_LENGTH_DATA
MAX_LENGTH_COLUMN
EMPLOYEE
NAME
48
50
EMPLOYEE
CITY
95
100
EMPLOYEE
AGE
2
() (don't need)
So we will have 1 line per column and table, if my table have 5 columns i will have 5 lines.
I've tried many solutions with LOOP, CURSOR and now TYPE OBJECT but i'm doing something wrong i know but can't figure out what it is.
CREATE OR REPLACE TYPE t_output_allColumns FORCE AS OBJECT
(maxLengthColumn NUMBER,
COLUMN_NAME VARCHAR2(80),
TABLE_NAME VARCHAR2(80));
/
CREATE OR REPLACE TYPE output_allColumns FORCE AS TABLE OF t_output_allColumns;
DECLARE
maxlengthTab output_allColumns;
v_requete_maxLength varchar2(4000);
TYPE MyCurTyp IS REF CURSOR;
c1 MyCurTyp;
v_column_name VARCHAR2(400);
v_table_name VARCHAR2(400);
begin
maxlengthTab:= output_allColumns();
OPEN c1 FOR 'select TABLE_NAME, COLUMN_NAME from ALL_TAB_COLUMNS';
FETCH c1 INTO v_column_name , v_table_name;
v_requete_maxLength := 'SELECT MAX( LENGTH(' || v_column_name ||'), ' || v_column_name ||',' || v_table_name ||' FROM ' ||v_table_name;
EXECUTE IMMEDIATE v_requete_maxLength BULK COLLECT INTO maxlengthTab;
dbms_output.put_line(output_allColumns);
CLOSE c1;
END;
Here is a script i tried, first thing i do is to select all columns from my schema (no problem with this, i already printed them to test and it's good)
But the main probleme is when i try to use dynamic SQL on my result
I try SELECT MAX( LENGTH(' || Colum_name i get from my 1st request||'), ' || Colum_name i get from my 1st request||',' || Table_name i get from my 1st request||' FROM ' ||Table_name i get from my 1st request; and this is where I'm stuck, I can't store each result and display it.
You can use a pipelined function.
Given the types:
CREATE TYPE t_output_allColumns AS OBJECT(
OWNER VARCHAR2(80),
TABLE_NAME VARCHAR2(80),
COLUMN_NAME VARCHAR2(80),
maxLengthData NUMBER,
maxLengthColumn NUMBER
);
CREATE TYPE output_allColumns AS TABLE OF t_output_allColumns;
Then the function:
CREATE FUNCTION column_details(
i_owner IN VARCHAR2
)
RETURN output_allcolumns PIPELINED
IS
v_data_length NUMBER;
BEGIN
FOR r IN (
SELECT owner,
table_name,
column_name,
data_length
FROM all_tab_columns
WHERE owner = i_owner
)
LOOP
EXECUTE IMMEDIATE
'SELECT MAX(LENGTH("'||r.column_name||'")) FROM "'||r.owner||'"."'||r.table_name||'"'
INTO v_data_length;
PIPE ROW (
t_output_allcolumns(
r.owner,
r.table_name,
r.column_name,
v_data_length,
r.data_length
)
);
END LOOP;
END;
/
Then you can use:
SELECT * FROM TABLE(column_details('SCHEMA_NAME'));
Which outputs:
OWNER
TABLE_NAME
COLUMN_NAME
MAXLENGTHDATA
MAXLENGTHCOLUMN
SCHEMA_NAME
EMPLOYEES
NAME
48
50
SCHEMA_NAME
EMPLOYEES
AGE
2
22
SCHEMA_NAME
EMPLOYEES
CITY
95
100
db<>fiddle here

Calling table name IN parameter for procedure and outputting every column name in that table

Sorry if this is not a good question, I feel like it should be simple but I'm not getting the result I want :(
I created the a stored procedure that prints out all of my table names in USER_TABLES. Now what I want to do now is call another procedure within the first procedure to output all of the columns in my tables.
What I have:
create or replace PROCEDURE ColumnNames(
newColumn.Table_Name IN varchar2
)
AS
CURSOR newColumn IS
SELECT COLUMN_NAME
FROM USER_TABLES;
CurrentRow newColumn%ROWTYPE;
BEGIN
FOR CurrentRow IN newColumn LOOP
DBMS_OUTPUT.PUT_LINE(CurrentRow.Column_Name);
END LOOP;
END;
/
Edited to add error message:
Error(3,10): PLS-00103: Encountered the symbol "." when expecting one of the following:
in out ... long double ref char time timestamp interval date binary
Thanks for your help!
Use user_tab_columns instead of user_tables.
Variable newColumn.Table_Name does nothing in your code and has incorrect name(contains dot symbol)
Also cursor and variable declaration are excessive in current case.
If you need to print all column names for particular table you can use
create or replace procedure print_columns(tab_name in varchar2) is
begin
for col in (select column_name from user_tab_columns where table_name = tab_name) loop
dbms_output.put_line(col.column_name);
end loop;
end;
/

PL/SQL instruction to get numeric column precision

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

DB Procedure to Dynamically update DB columns

Requirement is to update in db column only those column which are edited from frontend.
Logic i am using is to send 2 arrays from java code to DB procedire.
1st array is: Column_array which contains column names which have to be updated.
&
2nd array : value_array which contains the values of columns respoctive to the column_array.
From JAVA:
Array value_array = ((OracleConnection) dbConnection).createOracleArray("STRING_ARRAY",
valueList.toArray());
Array param_array = ((OracleConnection) dbConnection).createOracleArray("STRING_ARRAY",
paramList.toArray());
stmt = dbConnection.prepareCall(SqlConstants.UPDATE_SUBSCRIBER_CONFIG_IN_BOLTES);//
stmt.setLong(1, 3628);
stmt.setLong(2, 3629);
stmt.setLong(3, 3632);
stmt.setArray(4, param_array);
stmt.setArray(5, value_array);
int count = stmt.executeUpdate();
NOW at DB side:
how can i iterate over this list to update and set this in SET clause???
PROCEDURE update_subscriber_config (
p_app_id VARCHAR2,
p_service_id VARCHAR2,
p_pubsub_id VARCHAR2,
column_list string_array,
value_list string_array
)
AS
BEGIN
FOR a IN 1..column_list.count LOOP
update bolt_oracle_pubsub_config set
column_list(a)=value_list(a),
...how to do iteration here???
where APP_ID = p_app_id AND SERVICE_ID = p_service_id AND PUBSUB_ID = p_pubsub_id;
END LOOP;
END update_subscriber_config;
PLEASE HELP.
I found the solution which was simpler than array iteration.
Solution is to use COALESCE method.
PROCEDURE TEST (
p_app_id NUMBER,
p_service_id NUMBER,
p_pubsub_id NUMBER,
p_pubsub_name VARCHAR2,
p_host VARCHAR2,
p_user_name VARCHAR2,
p_auth_key VARCHAR2
)
AS
BEGIN
update bolt_elastic_pubsub_config set
PUBSUB_NAME = coalesce(p_pubsub_name, PUBSUB_NAME),
HOST = coalesce(p_host, HOST),
USER_NAME = coalesce(p_user_name, USER_NAME),
AUTH_KEY = coalesce(p_auth_key, AUTH_KEY)
where APP_ID = p_app_id AND SERVICE_ID = p_service_id AND PUBSUB_ID = p_pubsub_id;
END TEST;
You have to use dynamic SQL to assemble bespoke update statements. It will look something like this:
PROCEDURE update_subscriber_config (
p_app_id VARCHAR2,
p_service_id VARCHAR2,
p_pubsub_id VARCHAR2,
column_list string_array,
value_list string_array
)
AS
stmt varchar2(32767);
BEGIN
stmt := 'update bolt_oracle_pubsub_config set ';
FOR a IN 1..column_list.count LOOP
if a != 1 then
stmt := stmt ||', ';
end if;
stmt := stmt || column_list(a) ||'=''' ||value_list(a)||'''';
END LOOP;
stmt := stmt ||
' where APP_ID = :p1 AND SERVICE_ID = :p2 AND PUBSUB_ID = :p3';
execute immediate stmt using p_app_id , p_service_id , p_pubsub_id;
END update_subscriber_config;
This is a very crude implemnentation, as it assumes all the passed columns can be treated as strings. If your table has numeric or date columns you should think about handling data conversion, because that could become a problem.
Dynamic SQL is hard because it turns compilation errors into runtime errors. In your case you have a piece of code which potentially executes a different update statement every time you call it. So testing this procedure is a total nightmare. This means you have a heavy dependency on the front end passing arrays with valid contents.
Perhaps you could try avoiding one of the arrays which are the column array as Oracle SQL would tolerate if the value is null.
The below is the pseudo code.
PROCEDURE update_subscriber_config (p_app_id VARCHAR2,
p_service_id VARCHAR2,
p_pubsub_id VARCHAR2,
p_first_column VARCHAR2,
p_second_column VARCHAR2,
--column_list string_array,
i_array IN my_array_type)
AS
BEGIN
FORALL i IN 1 .. i_array.COUNT
UPDATE bolt_oracle_pubsub_config
SET your_first_column =
TREAT (i_array (i) AS my_array_type).column_array_name,
your_second_column =
TREAT (i_array (i) AS my_array_type).second_column_array_name
WHERE APP_ID = p_app_id
AND SERVICE_ID = p_service_id
AND PUBSUB_ID = p_pubsub_id;
END update_subscriber_config;
If any of the columns which are stated in the update SQL is empty, it would be null or empty in the table after executing the update. Having said that, ensure all NOT NULL or mandatory columns are populated.
I would reckon to avoid the dynamic SQL update due to error-prone. If you achieve the results without using dynamic SQL, then why the hassle with dynamic SQL update.

PL/SQL Dynamic Loop Value

My goal is to keep a table which contains bind values and arguments, which will later be used by dbms_sql. The below pl/sql example is basic, it's purpose is to illustrate the issue I am having with recalling values from prior loop objects.
The table account_table holds acccount information
CREATE TABLE account_table (account number, name varchar2(100)));
INSERT INTO mytest
(account, name)
VALUES
(1 ,'Test');
COMMIT;
The table MYTEST holds bind information
CREATE TABLE mytest (bind_value varchar2(100));
INSERT INTO mytest (bind_value) VALUES ('i.account');
COMMIT;
DECLARE
v_sql VARCHAR2(4000) := NULL;
v_ret VARCHAR2(4000) := NULL;
BEGIN
FOR I IN (
SELECT account
FROM account_table
WHERE ROWNUM = 1
) LOOP
FOR REC IN (
SELECT *
FROM mytest
) LOOP
v_sql := 'SELECT ' || rec.bind_value || ' FROM dual';
EXECUTE IMMEDIATE v_sql INTO v_ret;
dbms_output.put_line ('Account: ' || v_ret);
END LOOP;
END LOOP;
END;
/
I cannot store the name i.account and later use the value that object. My idea was to use NDS; but, while the value of v_sql looks ok (it will read "Select i.account from dual"), an exception of invalid identifier would be raised on i.account. Is there a way to get the value of the object? We are using Oracle 11g2.
Thanks!
Dynamic SQL will not have the context of your PL/SQL block. You cannot use identifiers that are defined in the PL/SQL block in your dynamic SQL statement. So you cannot reference i.account and reference the loop that you have defined outside of the dynamic SQL statement. You could, of course, dynamically generate the entire PL/SQL block but dynamic PL/SQL is generally a pretty terrible approach-- it is very hard to get that sort of thing right.
If you are trying to use the value of i.account, however, you can do something like
v_sql := 'SELECT :1 FROM dual';
EXECUTE IMMEDIATE v_sql
INTO v_ret
USING i.account;
That doesn't appear to help you, though, if you want to get the string i.account from a table.
Are you always trying to access the same table with the same where clause, but just looking for a different column each time? If so, you could do this:
p_what_I_want := 'ACCOUNT';
--
SELECT decode(p_what_I_want
,'ACCOUNT', i.account
, 'OTHER_THING_1', i.other_thing_1
, 'OTHER_THING_2', i.other_thing_2
, 'OTHER_THING_1', i.default_thing) out_thing
INTO l_thing
FROM table;
The above statement is not dynamic but returns a different column on demand...