Oracle pl/sql Array - oracle

let's see if somebody can help me, I need to delete rows from different tables and I did think to do it using an array so i wrote this :
DECLARE
TYPE mytype_a IS TABLE OF VARCHAR2(32) INDEX BY BINARY_INTEGER;
mytype mytype_a;
BEGIN
mytype(mytype.count + 1) := 'MYTABLE';
FOR i IN 1 .. mytype.count
LOOP
DELETE mytype(i) WHERE valid = 'N';
END LOOP;
END;
Trying to run this piece of code using sqldeveloper I get the ORA-00933 command not properly ended, if I put directly the table name it works, what am I doing wrong?
Thank you.
Thank you very much guys, it works perfectly.

This is not the correct approach. You have to use Dynamic SQL for this -
DECLARE
type mytype_a is table of varchar2(32) index by binary_integer;
mytype mytype_a;
stmt varchar(500) := NULL;
BEGIN
mytype (mytype.count + 1) := 'MYTABLE';
for i in 1..mytype.count loop
stmt := 'DELETE FROM ' || mytype(i) || ' where valid =''N''';
EXECUTE IMMEDIATE stmt;
end loop;
END;

You would need to use dynamic SQL, concatenating the table name from the collection into the statement, inside your loop:
execute immediate 'DELETE FROM ' || mytype(i) || ' where valid = ''N''';
Or you can put the statement into a variable so you can display it for debugging purposes, and then execute that, optionally with a bind variable for the valid value:
stmt := 'DELETE FROM ' || mytype(i) || ' where valid = :valid';
dbms_output.put_line(stmt);
execute immediate stmt using 'N';
dbms_output.put_line('Deleted ' || sql%rowcount || ' row(s)');
... which I've made also display how many rows were deleted from each table. Note though that you shoudln't rely on the caller being able to see anything printed with dbms_output - it's up to the client whether it shows it.
The whole anonymous block would then be:
DECLARE
type mytype_a is table of varchar2(32) index by binary_integer;
mytype mytype_a;
stmt varchar2(4000);
BEGIN
mytype (mytype.count + 1) := 'MYTABLE';
for i in 1..mytype.count loop
stmt := 'DELETE FROM ' || mytype(i) || ' where valid = :valid';
dbms_output.put_line(stmt);
execute immediate stmt using 'N';
dbms_output.put_line('Deleted ' || sql%rowcount || ' row(s)');
end loop;
END;
/
You could use a built-in collection type to simplify it even further.
db<>fiddle showing some options.
Hopefully this doesn't apply, but if you might have any tables with quoted identifiers then you would need to add quotes in the dynamic statement, e.g.:
stmt := 'DELETE FROM "' || mytype(i) || '" where valid = :valid';
... and make sure the table name values in your collection exactly match the names as they appear in the data dictionary (user_tables.table_name).

Related

How to handle different exceptions in PL/SQL differently?

My stored-procedure is looping executing different statements. I want to handle the following situations:
When the statement returns nothing (no_data_found), I want to quietly skip the rest of the loop (continue).
When the statement causes an error of any type, I want to report it, and then skip the rest of the loop (continue);
When the statement finds rows, I want to report it.
The code looks like:
...
LOOP
stmt := 'select * ......';
BEGIN
EXECUTE IMMEDIATE stmt;
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL;
WHEN OTHERS THEN
dbms_out.put_line(stmt || ': ' || SQLCODE);
CONTINUE;
END;
dbms_out.put_line('Found! Use: ' || stmt);
END LOOP;
The above elicits no errors, but the Found-line is printed for every loop-iteration, including for statements, that yield no results...
Why is the CONTINUE-directive ignored -- am I wrong expecting the directive to be obeyed for any exception -- be it NO_DATA_FOUND or anything else?
In your exception block, the action for your NO_DATA_FOUND handler is NULL - so it executes the NULL statement (i.e. does nothing) and falls out of the BEGIN-END block, hitting the dbms_out.put_line('Found! Use: ' || stmt); statement. The only handler which will execute CONTINUE; is the WHEN OTHERS.
One way to get the behavior you describe is to do a SELECT COUNT(*)... into a numeric variable and then just check to see how many rows are returned:
DECLARE
csr SYS_REFCURSOR;
nCount NUMBER;
BEGIN
LOOP
stmt := 'SELECT COUNT(*) FROM (SELECT * from ... WHERE ...)';
OPEN csr FOR stmt;
FETCH csr INTO nCount;
CLOSE csr;
IF nCount > 0 THEN
dbms_out.put_line('Found! Use: ' || stmt);
ELSE
dbms_out.put_line(stmt || ': ' || SQLCODE);
END IF;
END LOOP;
END;
Of course this is not really valid as there's no way for the value of stmt to change, but I suspect your "real" code handles that.

Procedure Results with Variable Column Alias - PLSQL/Oracle

I've got this procedure working in TOAD/PLSQL but, would like the alias for the first column to be set to the field_name argument passed to the procedure. I think I need to build the query as a string like,
query := 'Select 1 as ' || field_name || ' From Dual';
But am not getting it right. Is what I have in mind possible?
Thanks and the working code I'm trying to modify is below.
Create or Replace Procedure Delete_Me(field_name NVarChar2)
as
result_set sys_refcursor;
BEGIN
open result_set for
Select
Elapsed_Time((Select Start_Time From Temp_Time1)) as field_name
,To_Char(SysDate, 'HH12:MI:SS AM') as Time_Completed
,Elapsed_Time((Select Start_Time From Temp_Time0)) as Process_So_Far
From
Dual;
DBMS_SQL.RETURN_RESULT(result_set);
End;
After comment:
I pass the procedure a string and its valued is placed in, "field_name." I would like the alias of the first column to adopt the value of field_name. So if I call the procedure thusly:
BEGIN
DeleteMe('Random_Column_Name');
END;
The first column name would be called, "Random_Column Name." If I called the procedure this way:
BEGIN
DeleteMe('Different_Column_Name');
END;
The first column would be names, "Different_Column_Name."
After Dmitry's second comment:
It doesn't mean anything. It's an example of what I've tried and failed to get to work.
I understand that you need to make a dynamic query, the way to do that is sometihing like this:
DECLARE
TYPE ty_refcur IS REF CURSOR;
c_mycur ty_refcur;
whatever varchar2(200) := 'whatever';
my_query varchar2(500);
BEGIN
my_query := 'Select ''hello''as '||whatever||' from dual';
OPEN c_mycur FOR my_query;
--whatever you want to do
CLOSE c_mycur;
END;
Here's what I finally came up with. Stanimir helped me understand how variables are used. Thanks for that!
Create or Replace Procedure Report_Elapsed_Time(field_name0 NVarChar2,
field_name1 NVarChar2)
as
result_set sys_refcursor;
query VarChar2(30000);
BEGIN
query := 'Select
Elapsed_Time((Select Start_Time From Temp_Time1)) as ' || Replace(field_name0, ' ', '_') || '
,To_Char(SysDate, ''HH12:MI:SS AM'') as Time_Completed
,Elapsed_Time((Select Start_Time From Temp_Time0)) as ' || Replace(field_name1, ' ', '_') || '
From
Dual';
open result_set for query;
DBMS_SQL.RETURN_RESULT(result_set);
End;

oracle bulk collect and reading the data

I have created below proc to read all the data from one table and populate it in a grid in .net form.
CREATE OR REPLACE PROCEDURE EVMPDADM.GETALLBATCHES_ARTICLE_57(p_batchstatus OUT XEVMPD_SUBMITTEDBATCH%ROWTYPE )
IS
TYPE batch_status IS TABLE OF XEVMPD_SUBMITTEDBATCH%ROWTYPE INDEX BY PLS_INTEGER;
l_batchstatus batch_status;
BEGIN
SELECT * BULK COLLECT INTO l_batchstatus FROM XEVMPD_SUBMITTEDBATCH ;
FOR i IN 1..l_batchstatus.count LOOP
p_batchstatus:= l_batchstatus(i);
END LOOP;
END GETALLBATCHES_ARTICLE_57;
To test if the proc is running fine I tried to print the data by using below Pl-sql block:
DECLARE
v_batchstatus XEVMPD_SUBMITTEDBATCH%ROWTYPE;
BEGIN
EVMPDADM.GETALLBATCHES_ARTICLE_57(v_batchstatus);
DBMS_OUTPUT.PUT_LINE( v_batchstatus.Batch_id || ' ' || v_batchstatus.BATCH_DESCRIPTION || ' ' || v_batchstatus.STATUS || ' ' ||v_batchstatus.RECORD_STATUS || ' ' ||v_batchstatus.NUMBER_OF_RECORDS);
END;
/
But from this process I am getting the last row only.
I want to print all the records present in the table.
can any one please help me to figure out what is wrong in the above code.
The error messages are very obvious. You are calling your procedures with:
Wrong number of arguments for EVMPDADM.GETALLBATCHES_ARTICLE_57: It has one OUT parameter. So you need to pass that parameter.
Wrong type of argument for DBMS_OUTPUT.PUT_LINE: It has one IN VARCHAR2 parameter, and not XEVMPD_SUBMITTEDBATCH%ROWTYPE. Read here
So, it should be this way:
DECLARE
v_batchstatus XEVMPD_SUBMITTEDBATCH%ROWTYPE;
BEGIN
v_batchstatus:= EVMPDADM.GETALLBATCHES_ARTICLE_57(v_batchstatus);
--use DBMS_OUTPUT.PUT_LINE for every column of XEVMPD_SUBMITTEDBATCH separately after you convert them to varchar2 if they are not.
END;
/
Besides, the procedure this way will return only the last row. So you might want to change that.
If you want to print all the records from the table, you need to add DBMS_OUTPUT.PUT_LINE inside the loop, it will become like this:
FOR i IN 1..l_batchstatus.count LOOP
p_batchstatus:= l_batchstatus(i);
dbms_output.put_line( p_batchstatus.col1 || ' ' || p_batchstatus.col2 || ... );
END LOOP;
Where col1, col2, ... are the columns names of XEVMPD_SUBMITTEDBATCH given they are of the type VARCHAR2. Or you will need extra processing

Reusing PROCEDURE/ FUNCTION whichever to do the same thing using different values

I have many procedures that do the same thing:
they refresh materialized view and check if the count is not 0, then push that data into production tables. this is the skeleton of what each one does, the only thing that changes is the name of the materialized view. I thought about creating one function that will take in the name of the MV and process it, but it is not working :(
create or replace
function REFRESH_MV (mv_to_refresh IN VARCHAR2)
RETURN VARCHAR2
AUTHID CURRENT_USER
AS
COUNTS INT;
begin
DBMS_MVIEW.REFRESH(mv_to_refresh,'C');
COMMIT;
SELECT COUNT(*) INTO COUNTS FROM 'SEMANTIC.' || mv_to_refresh;
IF COUNTS = 0 THEN
RETURN 'SEMANTIC.' || mv_to_refresh || ' is empty';
ELSE
'SEMANTIC_READ_ONLY.' || RELOAD_TABLE(mv_to_refresh);
RETURN 'SEMANTIC_READ_ONLY.' || mv_to_refresh || ' has been refreshed today';
END IF;
EXCEPTION WHEN OTHERS THEN NULL;
end;
You have to use EXECUTE IMMEDIATE or DBMS_SQL to do that; the first one should be easier to use in your case.
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM 'SEMANTIC.' || mv_to_refresh INTO COUNTS;
should do the trick.
You should use dynamic SQL for this purpose:
CREATE OR REPLACE FUNCTION REFRESH_MV (mv_to_refresh IN VARCHAR2)
RETURN VARCHAR2
AUTHID CURRENT_USER
AS
COUNTS INT;
VSQL VARCHAR2(100);
begin
DBMS_MVIEW.REFRESH('SEMANTIC.' || mv_to_refresh, 'C');
COMMIT;
VSQL := 'SELECT COUNT(1) FROM SEMANTIC.' || mv_to_refresh;
EXECUTE IMMEDIATE VSQL INTO COUNTS;
IF COUNTS = 0 THEN
RETURN 'SEMANTIC.' || mv_to_refresh || ' is empty';
ELSE
SEMANTIC_READ_ONLY.RELOAD_TABLE(mv_to_refresh);
RETURN 'SEMANTIC_READ_ONLY.' || mv_to_refresh
|| ' has been refreshed today';
END IF;
EXCEPTION
WHEN OTHERS THEN
RETURN 'Error has occured: ' || SQLERRM;
END;
Please make sure you pass view name without schema prefix as input parameter.
You should also note that scince it function it should return value or raise exception. But in you example function will return nothing in case of exception.
I didn't quite get the semantics of RELOAD_TABLE() procedure. In example given it is supposed to be a some procedure in SEMANTIC_READ_ONLY schema. In case you really need the appropriate function to be evaluated dynamically, you again can use dynamic SQL to construct the valid string contaning the code and call it:
vsql := 'begin SCHEMA_NAME.' || GET_PROCEDURE_FOR(mv_to_refresh) || '; end;';
execute immediate vsql;

Is it possible to parameterize a query inside pl sql?

The stored procedures being written here currently concats the parameters to the queries:
SELECT *
FROM Names
WHERE Name = ' || prmName || '
ORDER BY ' || prmSortField
Is it possible to parameterize this query inside the stored procedure? Possibly like:
query = 'select * From Names Where Name = #name Order By ' || prmSortField
call(query, prmName)
Note: In case you wonder why we do so, there are two common parameters for our sp's: sortFieldIndex and sortDirection. Since we cannot directly parameterize these, the query is dynamically generated. But other parameters make the queries open for injection. So I am looking a way to parameterize some of the parameters.
Absolutely. Use cursors.
DECLARE
CURSOR c1 (job VARCHAR2, max_wage NUMBER) IS
SELECT * FROM employees WHERE job_id = job AND salary > max_wage;
BEGIN
FOR person IN c1('CLERK', 3000)
LOOP
-- process data record
DBMS_OUTPUT.PUT_LINE('Name = ' || person.last_name || ', salary = ' ||
person.salary || ', Job Id = ' || person.job_id );
END LOOP;
END;
For a dynamic query with bind values, do this:
procedure p (prmName varchar2, prmSortField varchar2)
is
query varchar2(100);
rc sys_refcursor;
names_rec names%rowtype;
begin
query = 'select * From Names Where Name = :name Order By ' || prmSortField
open rc for query using prmName;
loop
fetch rc into names_rec;
exit when rc%notfound;
-- process this row
end loop;
close rc;
end;
For a more elaborate procedure that supports optional parameter values (but uses sys context), check out the following post on Asktom.com
PRATTY -- Thanks for the question regarding 'CURSOR'...

Resources