Very new to SQL in general.
Have seen a few examples on how to declare table as variable in PL/SQL, however, none of them seem to do what I need.
The procedure is quite simple, check for duplicate unique numbers in a table, eg:
select unique_id,
count(unique_id) as count_unique
from table_name
having count(unique_id)>1
group by unique_id
I would like to create a procedure that can be called and dynamically change the _name and the unique_id.
Something like:
declare
table_name is table:= table_1
unique_id varchar2(100):= unique_1
begin
select unique_id,
count(unique_id) as count_unique
from table_name
having count(unique_id)>1
group by unique_id
end;
/
If you want to change the table at runtime, you'd need dynamic SQL which means that you'd need to assemble the SQL statement you want in a string variable and execute that string. If you have a procedure, you'd need that procedure to do something with the results of the query. My guess is that you want to return a cursor.
Note that I'm not doing anything to validate the table and column names to avoid SQL injection attacks. You'd probably want to use dbms_assert to validate the input rather than blindly trusting the caller.
create or replace procedure get_duplicates( p_table_name in varchar2,
p_column_name in varchar2,
p_rc out sys_refcursor )
as
l_sql varchar2(1000);
begin
l_sql := ' select ' || p_column_name || ', ' ||
' count(' || p_column_name || ') as unique_count ' ||
' from ' || p_table_name ||
' group by ' || p_column_name ||
' having count(' || p_column_name || ') > 1';
dbms_output.put_line( l_sql );
open p_rc for l_sql;
end;
Related
I was performing an activity to identify eMail addresses based on certain pattern (#xyz.de). I initially tried checking the DBA_TAB_COLS [data dictionary] view but this just finds email column names and I manually need to check the big list of tables. Instead of doing that, is there is a more effective way to just fetch the the pattern value #xyz.de ?
Database - oracle 11g
Query used
SET SERVEROUTPUT ON 100000
DECLARE
lv_count number(10):=0;
l_str varchar2 (1000);
lv_col_name varchar2(255) :='EMAIL';
BEGIN
FOR V1 IN
(select distinct table_name
from dba_tab_columns
where column_name = lv_col_name
order by table_name)
LOOP
dbms_output.put_line(lv_col_name||' '||v1.table_name);
END LOOP;
END;
Please note that
I don't exactly know the table or column names.
The value #xyz.de can be in any schema and any table and any column. This has to be identified but in an effective way.
Any suggestions?
i have used the above block query to fetch the email column along with the table name , but how can i achieve by searching certain value #xyz.de using the dynamic sql ?
I don't know what you want to do with the values that you are trying to extract, so the below code just prints them. Refer to PL/SQL Dynamic SQL from the Oracle documentation.
declare
type EMAILS is ref cursor;
L_CURSOR EMAILS;
cursor EMAIL_COLS is
select OWNER
,TABLE_NAME
,COLUMN_NAME
from DBA_TAB_COLS
where COLUMN_NAME like '%EMAIL%'
and OWNER <> 'SYS';
L_SQL varchar2(200);
L_EMAIL varchar2(500);
begin
for REC in EMAIL_COLS
loop
L_SQL := 'select ' || REC.COLUMN_NAME || ' from ' || REC.OWNER || '.' || REC.TABLE_NAME || ' where ' || REC.COLUMN_NAME || ' like ''%xyz.de''';
open L_CURSOR for L_SQL;
loop
fetch L_CURSOR into L_EMAIL;
exit when L_CURSOR%notfound;
DBMS_OUTPUT.PUT_LINE(L_EMAIL);
end loop;
end loop;
end;
How do I get the data(i.e rows) from the column_name I retrieved from SQL statement? (Completely new to PL/SQL).
Here is my code:
create or replace procedure com_coll_cur
as
type comColcur is ref cursor;
com_col_cur comColcur;
sql_stmt varchar2(4000);
type newtab_field is table of comColcur%TYPE;
begin
sql_stmt :=
'select column_name from all_tab_cols where table_name in (''TAB1'', ''TAB2'') ' ||
'group by column_name having count(*)>1';
open com_col_cur for sql_stmt;
loop
fetch com_col_cur into newtab_field;
exit when com_col_cur%NOTFOUND;
end loop;
close com_col_cur;
end;
What I'm trying to do here is first find the common columns between the two tables. This part only grabs column_name but I also want the data in that common columns. So I used cursor to "point" that common column_name and used loop(fetch) to get the data inside that common column_name. Finally, I want to create new table with this common columns only with their data.
I am new to everything here and any help will be appreciate it.
You don't understand how works cursors and fetch.
Fetch get the data from the cursor, so in your procedure example you get only column names, not the data in the columns. To get data you need another cursor - select from the target table or use the dynamic sql.
This is a code that do what you describe. It is not clear to me how you want to store data from two tables - subsequently or in another manner. Let's assume that we store them subsequently. Also this code suggests than columns with the same names have the same datatypes. Part of this code (to make datatype) I get from another stackoverflow post to save time to write it:
How do I get column datatype in Oracle with PL-SQL with low privileges?
dbms_output.put_line - used to print sql statements that we create
declare
cSql varchar2(4000);
cCols varchar2(4000);
cNewTableName varchar2(30) := 'AABBCC';
cTb1 varchar2(30) := 'TAB1';
cTb2 varchar2(30) := 'TAB2';
begin
for hc in (
select T.column_name, T.typ
from
(
select column_name,
data_type||
case when data_precision is not null and nvl(data_scale,0)>0 then '('||data_precision||','||data_scale||')'
when data_precision is not null and nvl(data_scale,0)=0 then '('||data_precision||')'
when data_precision is null and data_scale is not null then '(*,'||data_scale||')'
when char_length>0 then '('||char_length|| case char_used
when 'B' then ' Byte'
when 'C' then ' Char'
else null
end||')'
end||decode(nullable, 'N', ' NOT NULL') typ
from all_tab_cols
where table_name in (cTb1, cTb2) ) T
group by T.column_name, T.typ having count(*) > 1)
loop
cSql := cSql || case when cSql is null then null else ',' end || hc.column_name || ' ' || hc.typ;
cCols := cCols || case when cCols is null then null else ',' end || hc.column_name;
end loop;
if (cSql is not null) then
-- First drop table if it exists
for hc in (select * from all_objects where object_type = 'TABLE' and object_name = cNewTableName)
loop
execute immediate 'drop table ' || hc.object_name;
end loop;
-- create table
cSql := 'create table ' || cNewTableName || '(' || cSql || ')';
dbms_output.put_line(cSql);
execute immediate cSql;
-- insert data
cSql := 'insert into ' || cNewTableName || '(' || cCols || ') select ' || cCols || ' from ' || cTb1;
dbms_output.put_line(cSql);
execute immediate cSql;
cSql := 'insert into ' || cNewTableName || '(' || cCols || ') select ' || cCols || ' from ' || cTb2;
dbms_output.put_line (cSql);
execute immediate cSql;
end if;
end;
I have declared the following procedure:
CREATE OR REPLACE PROCEDURE MODIFY_NOT_NULL(
v_tbName IN VARCHAR2,
v_cName IN VARCHAR2,
v_defaultValue IN VARCHAR2 )
IS
v_is_null VARCHAR2(1);
BEGIN
SELECT nullable INTO v_is_null
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = v_tbName
AND COLUMN_NAME = v_cName;
IF v_is_null = 'Y' THEN
EXECUTE IMMEDIATE ('ALTER TABLE ' || v_tbName
|| ' MODIFY (' || v_cName
|| ' DEFAULT ' || v_defaultValue
|| ' NOT NULL )');
END IF;
END;
However when I execute my code:
BEGIN
modify_not_null('TABLE_NAME', 'COLUMN_NAME ' ,'0');
END;
/
I am getting a
"ORA-01403: No Data Found"
This exception will be usually thrown if the "SELECT INTO" statement does not return any value, however I will always get a value when I execute this:
Select nullable
from USER_TAB_COLUMNS
WHERE table_name = 'TABLE_NAME'
AND column_name = 'COLUMN_NAME';
When I execute the code above, I get "N" or "Y" as a result. So I always get a result. I don't know why this exception is thrown
Your call contains a trailing space:
modify_not_null('TABLE_NAME', 'COLUMN_NAME ' ,'0');
^
So proc throws no data found because 'COLUMN_NAME ' != 'COLUMN_NAME'
Use upper(trim(v_cName)) to prevent typos causing errors. Apply on all parameters.
You are passing v_defaultValue param to column name.
Change procedure to
SELECT nullable INTO v_is_null
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = v_tbName AND COLUMN_NAME = v_cName ;
Before your SELECT .... INTO, you have to make sure that there is something to select. Because depending on what user you are, and what parameters you give, there may be no data in your table.
A simple way would be to have a COUNT at the beginning before the SELECT:
CREATE OR REPLACE PROCEDURE MODIFY_NOT_NULL(
v_tbName IN VARCHAR2,
v_cName IN VARCHAR2,
v_defaultValue IN VARCHAR2 )
IS
v_is_null VARCHAR2(1);
v_count number;
BEGIN
-- added select count
SELECT count(1) INTO v_count FROM USER_TAB_COLUMNS WHERE TABLE_NAME = trim(v_tbName) AND COLUMN_NAME = trim(v_cName);
-- added if v_count=1
if v_count = 1 then
SELECT nullable INTO v_is_null FROM USER_TAB_COLUMNS WHERE TABLE_NAME = trim(v_tbName) AND COLUMN_NAME = trim(v_cName);
IF v_is_null = 'Y' THEN
EXECUTE IMMEDIATE ('ALTER TABLE ' || v_tbName || ' MODIFY (' || v_cName || ' DEFAULT ' || v_defaultValue || ' NOT NULL )');
END IF;
-- added
end if;
END;
/
Share and enjoy
stay classy :)
create or replace procedure modify_not_null(v_tbName in varchar2,
v_cName in varchar2,
v_defaultValue in varchar2) is
cursor c_tbl(cp_tbname in varchar2,
cp_cname in varchar2) is
select nullable
from user_tab_columns
where table_name = upper(cp_tbname)
and column_name = upper(cp_cname);
l_tbl c_tbl%rowtype;
begin
open c_tbl(cp_tbname => v_tbName,
cp_cname => v_cName);
fetch c_tbl into l_tbl;
close c_tbl;
if l_tbl.nullable = 'Y' then
execute immediate 'alter table ' || v_tbName || ' modify (' || v_cName ||
' default ' || v_defaultValue || ' not null )';
end if;
exception
when others then
raise_application_error(-20000, dbms_utility.format_error_stack);
end modify_not_null;
I have a requirement to insert bulk data through procedure but the table and columns will be dynamic.
Suppose for one account there is one table and they have their configured columns, similarly there can be 1000 of such accounts, so there will 1000 number tables and their columns.
How can we achieve this in a single procedure?
A very primitive example, just so you get the feeling:
CREATE OR REPLACE PROCEDURE yourProcedure (tableName VARCHAR2,
colName1 VARCHAR2, colName2 VARCHAR2, colName3 VARCHAR2) IS
v_column VARCHAR2(30);
sql_stmt VARCHAR2(200);
BEGIN
sql_stmt := 'SELECT ' || colName1 || ', ' || colName2 || ', ' colName3 || ' FROM ' || tableName
EXECUTE IMMEDIATE sql_stmt;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Node Data Found');
END yourProcedure;
You could pass an array of columns instead of 3 columns only, if you the columns you need to select are not known beforehand.
You can also bind variables to the select statement adding the "USING" clause. Refer to documentation examples: http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/dynamic.htm#CHDGJEGD
I would like to create a generic Oracle PL/SQL Procedure which takes table name and column name as a parameter and then creates group by SQL againts the table name. Then basic structre of my procedure is this:
CREATE OR REPLACE PROCEDURE P_GROUPBY (
P_Table_Name IN ,
P_Column_Name IN ,
)
BEGIN
/**********************Check Parameters ************************/
IF (P_Table_Name IS NULL OR P_Column_Name IS NULL)
THEN
Raise_Application_Error (-20001, 'Parameter cannot be NULL');
END IF;
/**********************Select statment ************************/
SELECT P_Column_Name, COUNT(*) AS CNT
FROM P_Table_Name
GROUP BY P_Column_Name
ORDER BY P_Column_Name;
EXCEPTION
WHEN OTHERS
THEN
ROLLBACK;
DBMS_OUTPUT.Put_Line ('Cannot run the statement');
Raise_Application_Error (-20004,
'Could not run the procedure: ' || SQLERRM);
END P_GROUPBY;
How to determine the data type for the parameters? How this can be done?
You cannot issue SQL statement like this:
SELECT P_Column_Name, COUNT(*) AS CNT
FROM P_Table_Name
GROUP BY P_Column_Name
ORDER BY P_Column_Name;
The solution is to use dynamic sql:
v_sql := 'SELECT ' || P_Column_Name || ', COUNT(*) AS CNT' ||
'FROM ' || P_Table_Name ||
'GROUP BY ' || P_Column_Name ||
'ORDER BY ' || P_Column_Name;
EXECUTE IMMEDIATE v_sql;
Regarding How to determine the data type for the parameters? I am not sure what you mean. May be you want to determine the type of specific column in a table? If yes then you could query USER_TAB_COLS view:
select data_type from user_tab_cols
where table_name = P_Table_Name
and column_name = P_Column_Name;
One more addition: you don't need ROLLBACK in exception handling part as long as you don't modify any data in your procedure.