Display the output of EXECUTE IMMEDIATE statement - sql

I have a stored procedure to truncate the table whose name is passed as a parameter to the procedure.
create or replace procedure delete_data_from_table(table_id VARCHAR2)
is
cursor table_cur is
SELECT table_name FROM all_tables WHERE table_name LIKE '%' || table_id || '%';
tab_name VARCHAR2(25);
BEGIN
open table_cur;
LOOP
FETCH table_cur into tab_name;
exit when table_cur%notfound;
EXECUTE IMMEDIATE 'TRUNCATE TABLE ' || tab_name;
END LOOP;
close table_cur;
END;
I want to display the output of the execute immediate statement using DBMS_OUTPUT.PUT_LINE. Is it possible to do so?
Thanks in advance.

There is no native output from execute immediate or the statement you are running. When you truncate a table in a client it will usually report something like:
Table truncated.
or
Table MY_TABLE truncated.
or similar; but those messages are generated by the client running the command, not by the database.
You can just generate whatever message you want in your procedure, e.g.:
...
LOOP
FETCH table_cur into tab_name;
exit when table_cur%notfound;
EXECUTE IMMEDIATE 'TRUNCATE TABLE ' || tab_name;
DBMS_OUTPUT.PUT_LINE('Table ' || tab_name || ' truncated.');
END LOOP;
...
If the truncate statement fails for any reason then it will throw an exception and it won't reach the dbms_output line.
For other statement types you can optionally use SQL%ROWCOUNT to report the number of rows inserted/updated/merged/deleted to mimic what your client might show for those, but that doesn't apply for truncation.
Bear in mind though that someone else running your code might not have display of those messages enabled.

Related

Oracle stored procedure: FOR LOOP over a column from a variable

I want to save a list of column values into the variable input_cols and then loop over the values
CREATE OR REPLACE PROCEDURE map_values (mapping_table VARCHAR2(64)) AS
TYPE col_names IS TABLE OF VARCHAR2(64);
input_cols col_names;
BEGIN
EXECUTE IMMEDIATE
'SELECT COLUMN_NAME FROM SYS.DBA_TAB_COLUMNS
WHERE TABLE_NAME = '' ' || mapping_table || ' '' '
BULK COLLECT INTO (input_cols);
FOR in_col IN input_cols
LOOP
dbms_output.put_line ('test');
END LOOP;
END;
I am getting the error
PLS-00103: Encountered the symbol "LOOP" when expecting one of the following: * & - + / at mod remainder rem .. <an exponent (**)> ||
Although you can construct dynamic queries by concatenating values, it's generally better to use bind variables where possible, for example:
execute immediate
'select column_name from user_tab_columns where table_name = :b1'
bulk collect into input_cols
using p_table;
I recommend getting into the habit of anchoring types in your code to the corresponding database object, when there is one. For example, this:
mapping_table dba_tab_columns.table_name%type
instructs the compiler to look up the type of dba_tab_columns.table_name and use that. However, I would generally avoid the dba_ views in procedures like this and stick to user_ views, e.g. user_tab_columns, to limit them to objects you own. If you must use dba_ views, you should also include the table owner, as there may be more than one table with the same name.
I also prefer to name my parameters in a way that separates them from column names etc. There are various conventions (camelCase, prefixing with i_ for in or p_ for parameter, prefixing with the procedure name e.g. map_values.mapping_table), so pick one you like.
Putting that together, you get something like this:
create or replace procedure map_values
( p_table user_tab_columns.table_name%type )
as
type col_names is table of user_tab_columns.column_name%type;
input_cols col_names;
begin
execute immediate
'select column_name from user_tab_columns where table_name = :b1 order by column_id'
bulk collect into input_cols
using p_table;
for i in 1..input_cols.count loop
dbms_output.put_line(input_cols(i));
end loop;
end map_values;
Or, if you don't specifically need a collection and just want to loop through a result set:
create or replace procedure map_values
( p_table user_tab_columns.column_name%type )
as
columns_cur sys_refcursor;
colname user_tab_columns.column_name%type;
begin
open columns_cur for
'select column_name from user_tab_columns where table_name = :b1 order by column_id'
using p_table;
loop
fetch columns_cur into colname;
exit when columns_cur%notfound;
dbms_output.put_line(colname);
end loop;
close columns_cur;
end;
As Koen pointed out in the comments, though, there is no need for dynamic SQL in this example, so a much simpler version could be just:
create or replace procedure map_values
( p_table user_tab_columns.column_name%type )
as
begin
for r in (
select column_name from user_tab_columns
where table_name = p_table
order by column_id
)
loop
dbms_output.put_line(r.column_name);
end loop;
end map_values;
Word of advice: use a tool like SQL Developer to create your procedures. They show the compilation errors in a much clearer way. If you're new to PL/SQL, start with the very basics (empty procedure), compile, fix error if any and add code. There are 3 blocking issues in your code - debugging that is pretty hard.
I added a comment for each of the errors
create or replace PROCEDURE map_values
(mapping_table VARCHAR /* just define the datatype, not the precision */
)
AS
TYPE col_names IS TABLE OF VARCHAR2(64) INDEX BY BINARY_INTEGER;
input_cols col_names;
BEGIN
EXECUTE IMMEDIATE
'SELECT COLUMN_NAME FROM SYS.DBA_TAB_COLUMNS
WHERE TABLE_NAME = ''' ||mapping_table|| ''' '
BULK COLLECT INTO input_cols; /* no brackets needed */
dbms_output.put_line ('test:');
FOR in_col IN 1 .. input_cols.COUNT /* this is not a implicit cursor but a collection - you need to iterate over it.*/
LOOP
dbms_output.put_line ('test:'||input_cols(in_col));
END LOOP;
END;
/
Please do not use dynamic SQL for this use case, static SQL is sufficient and will be more efficient.
For looping, you have to use indexes and get the element from collection by index. There is no functionality that will enable you to directly assign collection element to a variable (like you are trying).
create or replace procedure map_values(mapping_table sys.dba_tab_columns%table_name)
as
type col_names is table of sys.dba_tab_columns.column_name%type;
input_cols col_names;
begin
select column_name
bulk collect into input_cols
from sys.dba_tab_columns
where table_name = mapping_table;
for i in 1 .. input_cols.count
loop
dbms_output.put_line(input_cols(i));
end loop;
end;

Oracle - BULK COLLECT INTO VARRAY used with Bind Variables only collecting column headers

Quick Disclaimer:
First thing out of the way, I know the preferred way of handling dynamic SQL in Oracle now is the DBMS_SQL package but unfortunately my application team does not have the grants to execute these procs at the moment and I am hoping to get this quick workaround knocked out before our DBA team gets back to me. Also, this database is on Oracle 12c.
Script Goal: I recently developed a Stored Proc (let's call it Original) that uses values in a "control table" to make a large number of updates to certain columns in a database with many schemas and tables. This script I am struggling with now (let's call it Test) is meant to be a quick loop through those columns affected by Original so as to verify that everything worked expectedly. Ultimately, I want to output the top 5 results of each changed column and hand a spooled file to my testing team for validation.
The control_table used in both scripts has 4 columns and looks like this:
OWNER
TABLE_NAME
COLUMN_NAME
ALGORITHM
Schema1
TableA
ColumnA
Method1
Schema1
TableB
ColumnB
Method1
Schema2
TableC
ColumnC
Method2
An example of one of the tables that gets updated by Original (let's say for TableA above) would be:
OtherCol1
OtherCol2
ColumnA
OtherCol3
Ignored
Ignored
UpdatedData1
Ignored
Ignored
Ignored
UpdatedData2
Ignored
Ignored
Ignored
UpdatedData3
Ignored
Issue with Test script: I have the dynamic SQL - I believe - working as it needs and I have been trying to figure out how best to print the results of the EXECUTE IMMEDIATE command to output. In doing some reading, I found that BULK COLLECT INTO should allow me to store the results of the dynamic queries into a COLLECTION which I can then print with dbms_output. I have attempted to do this with both a TABLE and a VARRAY but in both cases when I print, I am finding that the data stored in my collection is the column header of my dynamic query instead of the query values! The only thing I can think that could be the problem is the combining of BULK COLLECT INTO with the USING command when I run the dynamic statement but I have seen nothing in the documentation to indicate that these two commands are incompatible and my Test procedure below compiles without issue (and even seems to run ok).
Test Script:
SET SERVEROUTPUT ON SIZE UNLIMITED;
DECLARE
l_script VARCHAR2(500);
l_errm VARCHAR2(64);
TYPE results IS VARRAY(5) OF VARCHAR2(250);
va_cols results; --Defining here with a VARRAY but I have also tried with a table
BEGIN
FOR c_col IN(
SELECT owner, table_name, column_name, algorithm FROM control_list)
LOOP
l_errm := NULL;
va_cols := NULL;
BEGIN
dbms_output.put_line('Column '|| c_col.column_name || ' of table ' || c_col.owner ||
'.' || c_col.table_name || ' used algorithm ' || c_col.algorithm);
l_script := 'SELECT :1 FROM ' || c_col.owner || '.' || c_col.table_name ||
' WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY';
dbms_output.put_line('Script sent to Exec Immediate: ' || l_script); --Print l_script for debugging
EXECUTE IMMEDIATE l_script BULK COLLECT INTO va_cols USING c_col.column_name, c_col.column_name;
dbms_output.put_line(va_cols(1));
dbms_output.put_line(va_cols(2));
dbms_output.put_line(va_cols(3));
dbms_output.put_line(va_cols(4));
dbms_output.put_line(va_cols(5));
EXCEPTION
WHEN OTHERS THEN
l_errm := SUBSTR(SQLERRM, 1, 64);
dbms_output.put_line(' ERROR: ' || l_errm || '. Skipping row');
CONTINUE;
END;
END LOOP;
END;
/
So my intended dbms_output of the script above is:
Column ColumnA of table Schema1.TableA used algorithm Method1
Script sent to Exec Immediate: SELECT :1 FROM SCHEMA1.TABLEA WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY
UpdatedData1
UpdatedData2
UpdatedData3
UpdatedData4
UpdatedData5
Instead, however, bizarrely, what I am getting when I run this is:
Column ColumnA of table Schema1.TableA used algorithm Method1
Script sent to Exec Immediate: SELECT :1 FROM SCHEMA1.TABLEA WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY
ColumnA
ColumnA
ColumnA
ColumnA
ColumnA
Has anyone seen this before and know what I am doing wrong? Thanks in advance!!
You can't use bind variables to change what columns you're referencing. You use bind variables to specify particular values at runtime. When you do
l_script := 'SELECT :1 FROM ' || c_col.owner || '.' || c_col.table_name ||
' WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY';
EXECUTE IMMEDIATE l_script BULK COLLECT INTO va_cols USING c_col.column_name, c_col.column_name;
you're telling Oracle that you want to select the literal string in the variable c_col.column_name. Not the column in the table by that name. Which is why every row returns that literal value.
You'd need to dynamically assemble the SQL statement with the column names, not try to use them as bind variables. So something like
l_script := 'SELECT ' || c_col.column_name ||
' FROM ' || c_col.owner || '.' || c_col.table_name ||
' WHERE ' || c_col.column_name || ' IS NOT NULL FETCH FIRST 5 ROWS ONLY';
EXECUTE IMMEDIATE l_script BULK COLLECT INTO va_cols;
This is approximately what you want. I outer cursor over tables and column to inspect that generate the dynamic SQL.
Inner loop reading the column values from the previous query
DECLARE
TYPE CurTyp IS REF CURSOR;
v_cursor CurTyp;
v_value VARCHAR2(200);
v_stmt_str VARCHAR2(200);
BEGIN
FOR c IN (
SELECT table_name, column_name FROM control_list)
LOOP
dbms_output.put_line('tab: '||c.table_name);
v_stmt_str := 'SELECT '||c.column_name||' FROM '|| c.table_name;
OPEN v_cursor FOR v_stmt_str;
LOOP
FETCH v_cursor INTO v_value;
EXIT WHEN v_cursor%NOTFOUND;
dbms_output.put_line('col: '||c.column_name||' val: '||v_value);
END LOOP;
END LOOP;
CLOSE v_cursor;
END;
/

He does not perform the procedure. Fail in first step. Oracle

I created procedure in oracle that drops my table and creates same table from my view. But I have some problems with running this procedure. First step with drop table works but copying it doesn't work.
It this a good procedure ?
create or replace PROCEDURE transfer_table (table_name IN VARCHAR2,tableFrom IN VARCHAR2) IS
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE ' || table_name;
EXECUTE IMMEDIATE 'CREATE TABLE ' || table_name || ' AS SELECT * FROM ' || tableFrom;
commit;
END transfer_table;
Next I click on this procedure and change the variable then view and click ok, but only the first step of dropping the table is working. What am I doing wrong ?
I think you need to catch the error if the table you are creating doesn't exist.
create or replace PROCEDURE transfer_table (table_name IN VARCHAR2,tableFrom IN VARCHAR2) IS
BEGIN
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE ' || table_name;
EXCEPTION WHEN OTHERS THEN
NULL;
END;
EXECUTE IMMEDIATE 'CREATE TABLE ' || table_name || ' AS SELECT * FROM ' || tableFrom;
commit;
END transfer_table;

Handling exceptions oracle creating table

I was wondering if there is someway i can handle a exception if i get an error while creating a table during a procedure.
IF testes=0 then
stmt:= 'create table ' || prefix || SII_BCK_TAB_ID_SEQ.nextval || ' AS SELECT * FROM '|| n_tab || ' WHERE 1=0';
EXECUTE IMMEDIATE stmt;
can i create a exception after executing the statement? what is the best process to handle errors while creating a table? or is it the same as handling dml statements?
can i insert something like savepoints?
thank you
If I were you, I'd create a separate procedure to handle the table creation and then have an exception clause. This makes it modular code, then; easy to unit test, etc.
e.g.: the procedure would look something like:
PROCEDURE create_table (in_new_table_name in varchar2,
in_old_table_name in varchar2)
is
E_TAB_EXISTS EXCEPTION;
PRAGMA EXCEPTION_INIT(E_TAB_EXISTS,-955);
BEGIN
execute immediate 'create table ' || in_new_table_name || ' AS SELECT * FROM '|| in_old_table_name || ' WHERE 1=0';
EXCEPTION
WHEN E_TAB_EXISTS THEN
NULL;
END create_table;
and you would call it like:
If testes = 0 then
create_table(in_new_table_name => prefix || sii_bck_tab_id_seq.nextval,
in_old_table_name => n_tab);
...
end if;
Ideally, you're creating your code in a package, and can simply have the new procedure as a separate procedure in the package.

Drop table having name: <table_name>_(sysdate-1) only if the table exist

I need to drop a table only if the table exist, can do it using plsql block
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE <table_name>;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942 THEN
RAISE;
END IF;
END
But in my case the table name has sysdate (02062017) eg table001_02072017 and i need to delete all such tables with sysdate-1.
How can i do so?
You can find the table with the given pattern from user_tables dictionary table and loop on the result to drop each one by one.
begin
for t in (select table_name from user_tables
where table_name like '%\_'||to_char(sysdate-1,'mmddyyyy') escape '\')
loop
execute immediate 'drop table ' || t.table_name;
end loop;
exception
/* Handle your exceptions here. */
end;
/
Using WHEN OTHERS in your exception handling is discouraged. You should explicitly handle the errors.
Dynamic sql will help you here just use
execute immediate 'drop table ' || table_name;
inside your procedure