Datawarehousing Automation using stored procedure - sql

I am trying to code a procedure where i can validate all the records are moved from source to target provided there is no transformation logic involved in between for that particular column. My approach is to take the group by count of source column and target column and match their count. If the count is 0 then all the records are matched for that particular column to the target table.
Further a minus from the 2 data group by count will provide the missing data.
Can any one help me further on this.
Stored procedure :
create or replace
PROCEDURE MOVE_CHECK
(SCHEMA_SOURCE IN VARCHAR2,SCHEMA_TARGET IN VARCHAR2, TABLE_SOURCE IN VARCHAR2, TABLE_TARGET IN VARCHAR2, COLUMN_SOURCE IN VARCHAR2,
COLUMN_TARGET IN VARCHAR2)
AS
A varchar2 (30);
B varchar2 (30);
C varchar2 (30);
D varchar2 (30);
E varchar2 (30);
F varchar2 (30);
COUNT_SOURCE number(38);
TEMP_1 VARCHAR2(500);
TEMP_2 VARCHAR2(500);
TEMP_3 VARCHAR2(500);
TEMP_4 VARCHAR2(500);
COUNT_QUERY number(38);
BEGIN
A:=SCHEMA_SOURCE;
B:=SCHEMA_TARGET;
C:=TABLE_SOURCE;
D:=TABLE_TARGET;
E:=COLUMN_SOURCE;
F:=COLUMN_TARGET;
-- checking the count of the source records
TEMP_1 :='select count ( '|| E ||' ) from ' || C;
EXECUTE IMMEDIATE TEMP_1 INTO COUNT_SOURCE;
DBMS_OUTPUT.PUT_LINE ('source_count:'||Count_source);
TEMP_2 :='CREATE GLOBAL TEMPORARY TABLE SET_SOURCE AS (SELECT COUNT(1) AS COUNT_SOURCE, '|| E ||' from ' || C || ' GROUP BY ' || E||' )';
EXECUTE IMMEDIATE TEMP_2;
TEMP_3 :='CREATE GLOBAL TEMPORARY TABLE SET_TARGET AS (SELECT COUNT(1) AS COUNT_TARGET, ' || F||'FROM '||D||' GROUP BY ' ||D ||' )';
EXECUTE IMMEDIATE TEMP_3;
TEMP_4:= 'SELECT COUNT(1) FROM SET_SOURCE INTERSECT SET_TARGET ';
EXECUTE IMMEDIATE TEMP_4 INTO COUNT_QUERY;
DBMS_OUTPUT.PUT_LINE ('OUTPUT:'||COUNT_QUERY);
IF COUNT_QUERY <> 0
THEN DBMS_OUTPUT.PUT_LINE ('PLEASE CHECK');
ELSE DBMS_OUTPUT.PUT_LINE ('DONE- NO MISMATCH');
END IF;
END MOVE_CHECK;
I am unable to run execute Temp_2,Temp_3,Temp_4
Error:
ORA-00955: name is already used by an existing object
ORA-06512: at "YDSCST.MOVE_CHECK", line 35
ORA-06512: at line 16
source_count:7
Process exited.

It generally helps if you print out the dynamic SQL and try to run it manually; it can make syntax errors much easier to spot. In this case if you called the original procedure for the DUAL table you'd see:
CREATE GLOBAL TEMPORARY TABLE SET_SOURCE AS (SELECT COUNT(1), DUMMY )
from DUAL GROUP BY DUMMY )
You'd then get the same ORA-00923 if you ran that, and it suggests the problem might be before the from. And looking at the code it's clear that there was an extra closing parenthesis after the column name. But you spotted that yourself and have now moved on to the next error.
The next error after that should be ORA-00998: must name this expression with a column alias, because you haven't specified what the temporary table's columns should be called. You can do that in the select or in the create:
TEMP_2 :='CREATE GLOBAL TEMPORARY TABLE SET_SOURCE (ROW_COUNT, COLUMN_NAME) '
|| 'AS (SELECT COUNT(1), ' || E || ' from ' || C || ' GROUP BY ' || E || ')';
But you seem to have found and fixed that yourself too as you're now getting an ORA-06502, which is because your TEMP variables are smaller than your query string now needs to be. But you've also fixed that yourself now, making them 500 characters instead of 100.
Your TEMP_3 is missing a space before its from; the value of F is currently concatenated to that directly, making that an invalid statement. And the group by is using D (table name) instead of F (column name)
TEMP_3 :='CREATE GLOBAL TEMPORARY TABLE SET_TARGET (ROW_COUNT, COLUMN_NAME) '
|| 'AS (SELECT COUNT(1), ' || F || ' FROM ' || D || ' GROUP BY ' || F ||' )';
That would be more obvious if you were using the procedure argument names instead of meaningless (and pointless) local variables.
Creating global temporary tables, or any structure, within a procedure is generally not a good idea. The second time you call this they will already exist and it will error on that. If you really need them at all, you should create the temporary tables once outside the procedure as a separate task, and just populate them inside the procedure.

Related

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.

ERROR at line 1: ORA-00911: invalid character ORA-06512: at line 17

I am not a frequent user of database & once in a while i need to create/run/execute a few PL/SQL blocks. I have a similar situation right now, where I have the below block which while executing as SYS as SYSDBA in oracle database user throws the error :-
DECLARE
*
ERROR at line 1:
ORA-00911: invalid character
ORA-06512: at line 17
The PL/SQL Block is as below :-
DECLARE
TYPE RefCurTyp IS REF CURSOR;
alter_tbl VARCHAR2(200);
a_null CHAR(1);
tbl VARCHAR2(200);
clmn VARCHAR2(200);
dtyp VARCHAR2(200) ;
dlth VARCHAR2(200);
c RefCurTyp;
BEGIN
open c for 'select utc.table_name, utc.column_name, utc.data_type, utc.data_length FROM user_tab_columns utc, user_tables ut
WHERE utc.data_type = ''VARCHAR2'' AND utc.char_used =''B'' AND ut.table_name = utc.table_name';
LOOP
dbms_output.put_line(clmn);
FETCH c INTO tbl, clmn, dtyp, dlth;
EXIT WHEN c%NOTFOUND;
EXECUTE IMMEDIATE
'alter table '||tbl||' modify ('||clmn||' '||dtyp||'('||dlth||' CHAR))';
END LOOP;
CLOSE c;
END;
Even after pounding my head on it for 3 days i am unable to figure out the issue with this. Any input is appreciated.
While executing the same code via TOAD i get :-
You can use dbms_output to display the dynamic statement you are executing. To make sure you see and execute the same thing it's simpler to put the statement into a variable (you have one you aren't using). If you change the cursor type you don't need the local variables though, you can construct the statement as part of the cursor query, and then refer to it multiple times; you also won't have to escape your single quotes:
set serveroutput on
BEGIN
FOR r IN (
SELECT 'alter table ' || utc.table_name || ' modify (' || utc.column_name || ' '
|| utc.data_type || '(' || utc.data_length || ' CHAR))' as alter_stmt
FROM user_tab_columns utc
JOIN user_tables ut ON ut.table_name = utc.table_name
WHERE utc.data_type = 'VARCHAR2' AND utc.char_used ='B'
)
LOOP
dbms_output.put_line(r.alter_stmt);
execute immediate r.alter_stmt;
END LOOP;
END;
/
I suspect you have a table or column name that contains an invalid character and was created with a quoted identifier. That will probably be obvious from the output you see immediately before it fails. You can easily add double quotes to all of the identifiers by concatenating them as part of the statement generation:
BEGIN
FOR r IN (
SELECT 'alter table "' || utc.table_name || '" modify ("' || utc.column_name || '" '
|| utc.data_type || '(' || utc.data_length || ' CHAR))' as alter_stmt
FROM user_tab_columns utc
JOIN user_tables ut ON ut.table_name = utc.table_name
WHERE utc.data_type = 'VARCHAR2' AND utc.char_used ='B'
)
LOOP
dbms_output.put_line(r.alter_stmt);
execute immediate r.alter_stmt;
END LOOP;
END;
/
You said you're running this while connected as SYS, and you're looking at user_tables, so you are altering tables owned by SYS - which seems like a very bad idea. Even if you don't intend to modify built-in data dictionary tables, this will do so, and that would imply you've been creating your own objects in the SYS schema - which is generally considered a very bad idea. You should create a separate user account and only create and modify objects in that schema.
In my 11g instance SYS has a table that generates output from my first query as:
alter table _default_auditing_options_ modify (A VARCHAR2(1 CHAR));
... which would get ORA-00911 because of the underscores. If the identifiers are quoted then it would work:
alter table "_default_auditing_options_" modify ("A" VARCHAR2(1 CHAR));
... but, once again, you should not be altering built-in tables.

SQL Select Like Column Name from table

The goal is to create a Oracle Function that is capable of query column name off a token provided by the user as to create a function with such capabilities
select cols_like('%e%') from table
This is the point I am currently at
CREATE OR REPLACE Function COLS_LIKE
(v_search in VARCHAR2, v_table in VARCHAR2)
RETURN VARCHAR
IS
TYPE r_cursor IS REF CURSOR;
c_emp r_cursor;
crs_cols VARCHAR(255);
column_list VARCHAR(1000);
BEGIN
OPEN c_emp FOR
'select COLUMN_NAME from cols
where TABLE_NAME = ''' || v_table || '''
and column_name like ''' || v_search || '''';
LOOP
FETCH c_emp INTO crs_cols;
EXIT WHEN c_emp%NOTFOUND;
if column_list IS NULL THEN
column_list := crs_cols;
else
column_list := column_list || ', ' || crs_cols;
end if;
END LOOP;
RETURN column_list;
END;
Where you call the function such as this
Declare
tests VARCHAR(100);
sql_stmt VARCHAR2(200);
begin
tests := COLS_LIKE('%E%', 'table');
DBMS_OUTPUT.PUT_LINE(tests);
-- OR
sql_stmt := 'select ' || COLS_LIKE('%E%', 'table') || ' from table';
DBMS_OUTPUT.PUT_LINE(sql_stmt);
end;
The end goal would be something such as this
select COLS_LIKE('%E%', 'table') from table;
What modifications can I make to my function or how I am calling to so that this function can be applied correctly.
Why you'd want to do such a thing I've no idea but you could return an open cursor to PL/SQL fairly easily:
create or replace function cols_like (
PTable in varchar2
, PColumn in varchar2
) return sys_refcursor
l_cols varchar2(32767);
c_curs sys_refcursor;
begin
select listagg(column_name, ', ') within group (order by column_id)
into l_cols
from user_tab_cols
where table_name = upper(Ptable)
and column_name like '%' || upper(PColumn) || '%'
;
open c_curs for '
select ' || l_cols || '
from ' || Ptable;
return c_curs;
end;
/
Returning this to a standard SQL statement will be a lot more difficult, this is because in selecting this function you're only selecting one column's worth of data. You want to be able to select N columns, which means you need to start returning nested tables that have been dynamically created.
I'm sure it's possible; but, before you get anywhere close to starting to attempt to do this think about why you're doing it. Ask a question where you don't state your end goal but where you state what your actual problem is. Chances are there's a lot simpler solution.
I was also having same problem and found this, and it is working for me. I was just making comparison of mobile numbers from two tables and in one of the tables some numbers have 0 in start and some don't. Finally got solution and just added % in start:
a.number was having numbers, in some of them starting 0 was missing. and b.number was accurate.
b.number like CONCAT('%',a.number)

writing a generic procedure in oracle

i want to write procedure which accents name of 2 tables as arguments and then compare the number or rows of the 2.
Also i want to each field of the 2 columns.The row which has a missmatch shold be
moved to another error table.
Can anyone give a PL/SQL procedure for doing this.
I want to achive this in oracle 9
Pablos example wont work, the idea is right though.
Something like this do it.
create or replace PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2, T2 IN VARCHAR2) AS
v_r1 number;
v_r2 number;
v_sql1 varchar2(200);
v_sql2 varchar2(200);
BEGIN
v_sql1 := 'select count(1) from ' || T1;
v_sql2 := 'select count(1) from ' || T2;
EXECUTE IMMEDIATE v_sql1 into v_r1;
EXECUTE IMMEDIATE v_sql2 into v_r2;
dbms_output.put_line(T1 || ' count = ' || v_r1 || ', ' || T2 || ' count = ' || v_r2);
END;
DBMS_SQL is your friend for such operations.
You can use dynamic sql in PL/SQL. EXECUTE IMMEDIATE is your friend.
So, if you take two table names and trying to compare their row counts, you would do something like:
CREATE OR REPLACE PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2(200), T2 IN VARCHAR2(200)) AS
v_cursor integer;
v_r1 integer;
v_r2 integer;
v_sql varchar2(200);
BEGIN
v_sql := "select count(1) into :1 from " || T1;
EXECUTE IMMEDIATE v_sql USING v_r1;
v_sql := "select count(1) into :1 from " || T2;
EXECUTE IMMEDIATE v_sql USING v_r2;
-- compare v_r1 and v_r2
END;
Not 100% sure about PL/SQL syntax. It's been a while since the last time I coded in great PL/SQL!
You can achieve same results with similar approach using DBMS_SQL. Syntax is a little bit more complicated though.
I am just posting here to note that all answers gravitate around dynamic SQL, and do not turn the attention to the implied problems using it.
Consider passing the following string as first or second parameter:
dual where rownum = 0 intersect
SELECT 0 FROM dual WHERE exists (select 1 from user_sys_privs where UPPER(privilege) = 'DROP USER')
I'll leave it to that.
To answer your question - Oracle actually stores these values in the data dictionary, so if you have access to it:
CREATE OR REPLACE PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2, T2 IN VARCHAR2) AS
v_text varchar2(1000);
BEGIN
select listagg(owner || ' ' || table_name || ' count = ' || num_rows, ',')
into v_text
from all_tables --user, all or dba tables depends on requirements
where table_name in (T1, T2);
dbms_output.put_line(v_text);
exception
when others then raise; -- Put anything here, as long as you have an exception block
END COMPARE_ROW_COUNT;

generic stored procedure in oracle

I want to write a PLSQL stored procedure that accepts a table name as argument. This table is source table. Now inside my procedure i want to manipulate the fields of that table.
EX: I want to insert the records of this source table into another target table whose name is XYZ_<source table name>. The column names for source and target tables are the same. But there may be some extra fields in target table. How do i do it? The order of column names is not same.
You will have to build the INSERT statement dynamically.
create or replace procedure gen_insert
(p_src_table in user_tables.table_name%type
, p_no_of_rows out pls_integer)
is
col_str varchar2(16000);
begin
for rec in ( select column_name
, column_id
from user_tab_columns
where table_name = p_src_table
order by column_id )
loop
if rec.column_id != 1 then
col_str := col_str || ',' || rec.column_name;
else
col_str := rec.column_name;
end if:
end loop;
execute immediate 'insert into xyz_' || p_src_table || '('
|| col_str || ')'
|| ' select ' || col_str
|| ' from ' || p_src_table;
p_no_of_rows := sql%rowcount;
end;
/
Obviously you may want to include some error handling and other improvements.
edit
Having edited your question I see you have a special requirement for naming the target table which was obscured by the SO formatting.
You can do this using Dynamic SQL. Here's a link with basic info on Oracle Dynamic SQL