Flagging a column name of a query similar to table name - sql

Given a table with a naming scheme as follows:
Example 1:
INFO_APPLICATION_B
CORRESPOND_U|OBJECTIVE_U|APPLICATION_U|DENIED_U|ACCEPTED_U|
Example 2:
INFO_CITIZEN_B
REFUGEE_U|INCOME_U|EDUCATION_U|CITIZEN_U|
I would like to filter out the column name in the table that is similar (as seen in the examples) to the table name. Precisely, in the first example, column number 3 would ideally be flagged due to its similarity with its respective table name. The same idea follows in example 2 where column number 4 would be flagged.
How can I go about doing this in SQL?
I want an output which does not display the columns whose name is similar to the table name:
CORRESPOND_U|OBJECTIVE_U||DENIED_U|ACCEPTED_U|
Notice how "APPLICATION_U" is no longer there because it was similar to the table name "APPLICATION_B".

Loop through _v_relation_column and determine if the column contains the text in the table.
I suspect you mean "similar" in some other way, but I'm just following your example.
create or replace find_similar_columns(varchar(any))
returns varchar
language nzplsql
begin_proc
declare
input_table alias for $1;
included_columns varchar;
delimiter varchar;
column record;
filtered_column varchar;
begin
delimiter := '|';
included_columns := '';
for column in
select
attname
from
_v_relation_column
where
name = input_table order by attnum
loop
filtered_column := replace(column.attname, '\_U', '');
if input_table not like '%' || filtered_column || '%' then
included_columns := included_columns || delimiter || column.attname;
end if;
end loop;
end;
end_proc;

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

Executing GROUP BY clause with Aggregate Function inside FOR LOOP in PL/SQL

I have an EMPLOYEE table with some columns. I'm interested about three columns - ENTRY_TIME (NUMBER), EXIT_TIME (NUMBER), NAME (VARCHAR2). The NAME column has no distinct entries, i.e., a value may appear multiple times.
I have taken few distinct NAME values and I want to get the maximum EXIT_TIME and the minimum ENTRY_TIME for each of these selected NAME values from the entire data.
I have written the following PL/SQL block:
DECLARE
type namearray IS VARRAY(6) OF VARCHAR2(50);
name namearray;
total INTEGER;
EntryTime NUMBER;
ExitTime NUMBER;
employeeNames VARCHAR2(100);
BEGIN
name := namearray('Peter','Job','George','Hans','Marco','Alison');
total := name.count;
FOR i in 1 .. total LOOP
SELECT min(ENTRY_TIME), max(EXIT_TIME), NAME
INTO EntryTime, ExitTime, employeeNames
from EMPLOYEE
GROUP BY NAME having NAME = name(i);
dbms_output.put_line('EntryTime: ' || EntryTime || 'ExitTime: ' || ExitTime || 'Name: ' || employeeNames);
END LOOP;
END;
/
It is providing following error:
Error report -
ORA-01403: no data found.
ORA-06512: in line 12
01403. 00000 - "no data found"
*Cause: No data was found from the objects.
*Action: There was no data from the objects which may be due to end of fetch.
But data is there. I think there is something wrong with the query or the block itself.
Can someoneone help/suggest.
At first glance I was also confused with the HAVING CLAUSE as #APC commented but I believe the problem is with the employee names you passed as there could be names which are not available in the employee table which leads to NO_DATA_FOUND exception and you need to handle such scenarios with exception and other than that even though its working I would suggest to change the having clause to where clause
Try below,
DECLARE
type namearray IS VARRAY(6) OF VARCHAR2(50);
name namearray;
total INTEGER;
EntryTime NUMBER;
ExitTime NUMBER;
employeeNames VARCHAR2(100);
BEGIN
name :=namearray('Peter','Job','George','Hans','Marco','Alison');
total := name.count;
FOR i in 1 .. total
LOOP
BEGIN
SELECT min(1), max(1), NAME
INTO EntryTime, ExitTime, employeeNames
FROM EMPLOYEE
GROUP BY NAME
HAVING NAME = name(i);
dbms_output.put_line('EntryTime: ' || EntryTime || 'ExitTime: ' || ExitTime || 'Name: ' || employeeNames);
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('No data found for Name: ' || name(i));
END;
END LOOP;
END;
/
Consider the suggestion to have WHERE clause.:)
Why I am saying this is what you can find in the db<>fiddle
Another option to exception handling is to outer join the selected employee names with the table. This is accomplished by creating a collection (array) at the schema level, then using the TABLE function on that a variable of that collection type. See fiddle.
create type names_att is table of varchar2(50);
declare
name names_att := names_att('Peter','Job','George','Hans','Marco','Alison');
begin
for rec in
( select min(entrytime) entrytime
, max(exittime) exittime
, na.column_value employeenames
from table (name) na
left join employee emp on emp.employeenames = na.column_value
group by na.column_value
order by na.column_value
)
loop
dbms_output.put_line('Name: ' ||rec.employeenames) ||
' EntryTime: ' || nvl(to_char(rec.entrytime), '---') ||
' ExitTime: ' || nvl(to_char(rec.exittime), '---')
);
end loop;
end;
/
Note: I defined an Associative Array rather than a Varray for the names. I am not sure this technique works with varray. I have never seen any reason for a varray.

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

Get column names for a given table name from a function

I want to call table name manually input type then result should be table's details, I tried those function
1st function is working.
2nd function is not working.
1)
DECLARE
All_columns varchar;
Tab_name ALIAS FOR $1 ;
BEGIN
FOR All_columns IN SELECT column_name
FROM information_schema.columns
WHERE table_name=Tab_name
loop
raise notice 'Columns:%',All_columns;
end loop;
return All_columns;
END;
select test_levelfunction1('country_table');
It shows all columns of country table
2)
DECLARE
All_columns varchar ;
Tab_name ALIAS FOR $1 ;
BEGIN
FOR All_columns IN SELECT Tab_name.*
FROM Tab_name
loop
raise notice 'Columns:%',All_columns;
end loop;
return All_columns;
END;
The call select test_levelfunction1('country_table'); results in an error.
I need all the details from country_table.
How can I fix this function?
Neither function works, insofar as I read them. Or then you expect the first to return your input instead of column names.
You probably want to be using dynamic sql in both functions, e.g.:
EXECUTE $x$SELECT * FROM $x$ || Tab_name::regclass
http://www.postgresql.org/docs/current/static/plpgsql-statements.html
You can largely simplify this task. This SQL function does the job:
CREATE OR REPLACE FUNCTION f_columns_of_tbl(_tbl regclass)
RETURNS SETOF text AS
$func$
SELECT quote_ident(attname) AS col
FROM pg_attribute
WHERE attrelid = $1 -- valid, visible table name
AND attnum >= 1 -- exclude tableoid & friends
AND NOT attisdropped -- exclude dropped columns
ORDER BY attnum
$func$ LANGUAGE sql;
Call:
SELECT f_columns_of_tbl('myschema.mytable'); -- optionally schema-qualified name
For more details, links and a plpgsql version consider the related answer to your last question:
PLpgSQL function to find columns with only NULL values in a given table

Select from dynamic table names

Consider this query.
SELECT app_label || '_' || model as name from django_content_type where id = 12;
name
-------------------
merc_benz
DJango people might have guessed, 'merc_benz' is a table name in same db. I want to write some SQL migrations and I need to select results from such dynamic table names.
How can i use variable name as a table name???
Something like this...(see RETURN QUERY EXECUTE in the plpgsql portion of the manual)
CREATE function dynamic_table_select(v_id int) returns setof text as $$
DECLARE
v_table_name text;
BEGIN
SELECT app_label || '_' || model into
v_table_name from django_content_type where id = v_id;
RETURN QUERY EXECUTE 'SELECT a_text_column from '||quote_ident(v_table_name);
RETURN;
END
$$ LANGUAGE plpgsql;
It becomes a little more complex if you want to return more than a single column of one type - either create a TYPE that is representative, or if you're using all the columns of a table, there's already a TYPE of that table name. You could also specify multiple OUT parameters.
http://www.postgresql.org/docs/8.1/static/ecpg-dynamic.html
The basic answer I think is EXECUTE IMMEDIATE