pl/sql: select as argument/parameter of function in oracle - oracle

I've got problem with creating function. I'll try explain what the problem is:
The task is about creating function which is based on select first_name from employees where dept_id=10;. New function MUST have v_select_statement (which I wrote) as argument(sth like this: v_select_statement := select first_name from employees where dept_id=10;. Selet like you can see generating list of names. These names should be in new table which is created in this function too. New table should have name e.g new_table.
Problem is that I don't know how to do it. I tried something like this:
create or replace
FUNCTION create_new_tab (v_select_statement VARCHAR2) RETURN NUMBER
is
b first_name.employees%TYPE;
begin
--here i don`t know how to assign select statement to cursor or execute immediate. Any ideas? i tried create sth like:
-- execute immediate 'v_select_statement into b ';
execute immediate 'CREATE TABLE new_tab (i VARCHAR2(50))';
execute immediate 'insert into new_tab values (statement_result)';
--don`t know how and what to put as statement_result. I know that i could use cursor, but ---how in this case?
return 1;
exception
when others then
dbms_output.put_line(SQLERRM);
return 0;
END create_new_tab
;
Can you help me, because I give up. I have no idea how to solve this problem.
Please help me if you can.

What you can do is the following:
create or replace FUNCTION create_new_tab (v_select_statement VARCHAR2) RETURN NUMBER
is
b employees.first_name%TYPE;
cur SYS_REFCURSOR;
begin
OPEN cur for v_select_statement;
execute immediate 'CREATE TABLE new_tab (i VARCHAR2(50))';
LOOP
FETCH cur into b;
EXIT WHEN cur%NOTFOUND;
execute immediate 'insert into new_tab values (:statement_result)' USING b;
END LOOP;
CLOSE cur;
return 1;
exception
when others then
dbms_output.put_line(SQLERRM);
return 0;
END create_new_tab
;
Couldn't test it without the tables but it may point you in the right direction.
But as Avrajit already said, I would suggest creating the new_tab beforehand and then insert into that table normalle without Dynamic-SQL.
You also have to keep in mind, that this function can only be run once, as it will result in an error if you try to create the table when it already exists.

This is a Function created for your referenec.
What i suggest you to create a table beforehand and then
try to insert data into it. Please try this code and let me
know if it works for you. Thanks
Create or replace function test_func return number
as
cursor cr is select name from avrajit
where department='.NET';
name_av avrajit.name%type;
begin
for rec in cr
loop
insert into av_test(dpt) values(rec.name);
return 1;
end loop;
exception
when others then
return 0;
end test_func;

I dont know what is purpose of your function. But if you still want to do try this
create or replace
FUNCTION create_new_tab (v_select_statement VARCHAR2) RETURN NUMBER
is
b first_name.employees%TYPE;
begin
--here i don`t know how to assign select statement to cursor or execute immediate. Any ideas? i tried create sth like:
-- execute immediate 'v_select_statement into b ';
execute immediate 'CREATE TABLE new_tab (i VARCHAR2(50))';
execute immediate 'insert into new_tab values ( ' || v_select_statement || ' )'; --don`t know how and what to put as statement_result. I know that i could use cursor, but ---how in this case?
return 1;
exception
when others then
dbms_output.put_line(SQLERRM);
return 0;
END create_new_tab
;`

Related

How to execute script conditionally in oracle?

I have a long DDL data migration script which has 15 transaction blocks. For each block, it takes data from one table and then inserts into another table.
But now I have come across a scenario in which the database does not have that table. In this case my script does not compile even.
What I actually want is to check and return early if the table does not exist. If it does, then only continue further execution.
But the script does not even compiles since it has statement which contains a table name which is non-existent in that particular database.
After some googling, I found that we can use those statements in EXECUTE IMMEDIATE block. I have tried this, but haven't been able to get it compiled.
Here is the block -
DECLARE
V_OBJECT_NAME1 VARCHAR2(50);
V_STRING VARCHAR2(1000);
V_CONTINUE Boolean;
BEGIN
V_CONTINUE := true;
SELECT TABLE_NAME INTO V_OBJECT_NAME1 FROM USER_TABLES WHERE TABLE_NAME = 'OOLD_SFWID_CHG_LOG_HEADER';
EXCEPTION WHEN NO_DATA_FOUND THEN
V_CONTINUE := false;
IF V_CONTINUE then
--scubbber code
V_STRING := 'DECLARE
CURSOR C1 IS
SELECT * FROM OOLD_SFWID_CHG_LOG_HEADER;
BEGIN
OPEN C1;
CLOSE C1;
END;';
EXECUTE IMMEDIATE V_STRING
END IF;
END;
Please suggest how can we write it..

How to build a dynamic PLSQL query to fetch records?

I am trying to create a stored procedure in Oracle and make a dynamic query work to get a bunch of records. I have read many examples but so far I can't get this to work unless I do this:
CREATE OR REPLACE PROCEDURE GiveMeResultSet(
v_par1 IN CHAR,
v_par2 IN CHAR,
v_par3 IN CHAR,
v_par4 IN VARCHAR2,
v_par5 IN VARCHAR2,
v_par6 IN VARCHAR2,
cur_typ OUT SYS_REFCURSOR)
IS
BEGIN
OPEN cur_typ FOR 'select * from complex_query';
--CLOSE cur_typ;
END;
And I am executing it this way:
var c refcursor;
execute GiveMeResultSet(null,null,null,null,null,null,:c);
print c;
This way I get the header names and the records from the query, but I am not closing the cursor that is fetching the results. If I close it then I get nothing at all. I guess leaving it open could cause some kind of memory leak problem at some point.
I have seen similar cases in Oracle documentation where they do something like this:
sql_stmt := 'SELECT * FROM emp';
OPEN emp_cv FOR sql_stmt;
LOOP
FETCH emp_cv INTO emp_rec;
EXIT WHEN emp_cv%NOTFOUND;
-- process record
END LOOP;
CLOSE emp_cv;
But I have no clue what goes on the "process record" part of the code which would allow to get the whole set of records at the end, plus that my record has a complex structure that doesn't fit with a fixed set of fields as in a table.
Can you please show me the proper way to do this?.
Thanks a lot.
ok CodeRoller, here is my sample code for a unspecified ref cursor:
Code of the Function which returns the ref cursor:
create or replace function test_ref_cursor(pi_sql_statement in varchar2) return SYS_REFCURSOR is
result_cursor SYS_REFCURSOR;
begin
open result_cursor for pi_sql_statement;
return result_cursor;
end;
Now, in the next step I use this function to get data from v$parameter:
declare
type t_my_cursor is ref cursor;
my_cursor t_my_cursor;
l_rec v$parameter%rowtype;
begin
my_cursor := test_ref_cursor('select * from v$parameter');
loop
fetch my_cursor into l_rec;
exit when my_cursor%notfound;
dbms_output.put_line(l_rec.name || ' = ' || l_rec.value);
end loop;
close my_cursor;
end;
Take care, to close the ref-cursor!

In Oracle, how to catch exception when table used for the loop doesn't exists

I have this code:
BEGIN
FOR
U1 IN (SELECT * FROM SOME_USER.SOME_TABLE)
LOOP
BEGIN
-- do something;
END;
END LOOP;
END;
My problem is that sometimes SOME_USER.SOMETABLE do not exists but I want the rest of the script to be run. I know that checking if the table exists before running the code (in a IF ... THEN block) will not work because SELECT * FROM SOME_USER.SOME_TABLE is evaluated at compile time.
So another avenue is to run the SELECT with EXECUTE IMMEDIATE. This way it will be evaluated at run time and I would be able to catch the exception. Unfortunately I can't find a way to use EXECUTE IMMEDIATE with my U1 IN loop. How I should achieve this?
I'm on Oracle 11g and the SQL script is run from a batch script on Windows.
You can use the 'OPEN FOR' syntax:
DECLARE
CUR SYS_REFCURSOR;
<variables or record type> -- declare as appropriate
BEGIN
OPEN CUR FOR 'SELECT * FROM SOME_USER.SOME_TABLE';
LOOP
FETCH CUR INTO <variables or record type>;
EXIT WHEN CUR%NOTFOUND;
-- do something with variables or record
END LOOP;
CLOSE CUR;
END;
/
You need to fetch each row into variables or a record type, you can't use %ROWTYPE as the table still won't exist; and you can change to do bulk fetches if that's appropriate for your data volumes.
If you run that you'll still get ORA-00942, but if this is in a stored program you won't get it until run time, and you can now add an IF block to check for the table's existence before the OPEN.
Having a data model where objects may or may not exist at run-time seems rather fishy though...
Proposed solution with cursor is fine, I would add an exception handling for this particular exception : Table or view does not exist ORA-00942.
DECLARE
e_missing_t EXCEPTION;
pragma exception_init (e_missing_t,-942);
something number; --some variable you need to fetch to
CUR SYS_REFCURSOR;
BEGIN
OPEN CUR FOR 'SELECT * FROM SOME_USER.SOME_TABLE';
LOOP
FETCH CUR INTO something;
EXIT WHEN CUR%NOTFOUND;
-- do something with variables or record
END LOOP;
CLOSE CUR;
EXCEPTION
WHEN e_missing_t THEN
dbms_output.put_line('some_table is missing');
END;
/
You could possibly use a workaround -
Create a nested table type and store the results of the SELECT in it. Use that type to loop through values.
So,
SELECT data_obj(COL1, COL2) bulk collect into data_tbl_typ from data_table;
This part can go in the dynamic sql. (Remember to use bind variables)
And then just loop through this nested table type in your procedure.
Use the DBMS_SQL package to run the query.
Follow the examples in this Oracle documentation:
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm#sthref6147
pl/sql has the exception clause for that. OTHERS catches pretty much everything. You can deal with the exception in the function, or print a message and pass it back to the main. Break your function up into smaller functions and have each one catch it's own exception.
BEGIN
FOR
U1 IN (SELECT * FROM SOME_USER.SOME_TABLE)
LOOP
BEGIN
-- do something;
END;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Oh well. The table isn't there.');
--RAISE;
END;

Form dynamic query and execute in oracle

I want to form a dynamic query for drop and create view and execute it.
for C in Cursor
LOOP
ViewName :='View_'|| ID;
DropViewSQL := DropViewSQL || 'DROP VIEW '||ViewName ||';' ;
CreateViewSQL := CreateViewSQL || 'CREATE VIEW '|| ViewName ||' AS SELECT * from xyz;';
END LOOP;
//Some Insert and update statements to be executed before drop and create view basically i want to create a new view after these Insert and update statements
DBMS_OUTPUT.PUT_LINE(DropViewSQL);
DBMS_OUTPUT.PUT_LINE(CreateViewSQL);
EXECUTE IMMEDIATE DropViewSQL;
EXECUTE IMMEDIATE CreateViewSQL;
This gives error - PLS-00382: expression is of wrong type
I want to execute this dynamically formed query.
Solution:
I have used array instead of cursor to store the resultset. Performed the insert update and then loop on array.
Thanks for the suggestions..
You can't have multiple statements in one execute immediate call; you'd need to call that inside the loop:
for C in Cursor
LOOP
ViewName := 'View_'|| C.ID;
DropViewSQL := 'DROP VIEW ' || ViewName ;
CreateViewSQL := 'CREATE VIEW ' || ViewName || ' AS SELECT * from xyz';
DBMS_OUTPUT.PUT_LINE(DropViewSQL);
DBMS_OUTPUT.PUT_LINE(CreateViewSQL);
EXECUTE IMMEDIATE DropViewSQL;
EXECUTE IMMEDIATE CreateViewSQL;
END LOOP;
Notice that the dynamic SQL statement does not have a semicolon on the end. You also might not need to do a separate drop step; create or replace view might be more appropriate, since that retains grants.
Without any further information about the 'Some statements to be executed before drop and create view' part it isn't clear where those would fit in.
But that doesn't explain the PLS-00382. You haven't shown what Cursor is, and I suspect it doesn't like that. Since cursor is a reserved word yours (hopefully) won't be called that, but don't know if it's an explicit cursor declared earlier or an implicit cursor with the query in-place here. Either way you need to show what that is and what it is doing. If that is OK then maybe one of the other statements you're removed is causing the error. Without all the relevant code and the line number of the error, it's hard to guess.
If you really have to generate the commands together, then do something else before executing them, you could store them in a PL/SQL table:
DECLARE
cursor cur is select view_name as id from user_views;
/* or whatever your real cursor is */
type sqltab is table of varchar2(200);
dropsqltab sqltab;
createsqltab sqltab;
viewname varchar2(30);
BEGIN
dropsqltab := sqltab();
createsqltab := sqltab();
for C in cur
LOOP
ViewName := 'View_'|| C.ID;
dropsqltab.extend();
dropsqltab(dropsqltab.count) := 'DROP VIEW ' || ViewName ;
createsqltab.extend();
createsqltab(createsqltab.count) := 'CREATE VIEW ' || ViewName
|| ' AS SELECT * from xyz';
END LOOP;
/* other commands */
FOR i IN 1 .. dropsqltab.count LOOP
DBMS_OUTPUT.PUT_LINE(dropsqltab(i));
DBMS_OUTPUT.PUT_LINE(createsqltab(i));
EXECUTE IMMEDIATE dropsqltab(i);
EXECUTE IMMEDIATE createsqltab(i);
END LOOP;
END;
/
You still haven't said what relationship, if any, there is between the drop/create statements and the insert/update statements. If they are related you can extract values from the PL/SQL table more than once. If they aren't, well, I don't understand the restriction on the order things are done.

oracle drop index if exists

How do you drop an index only if it exists?
It seems simple but I did found anything on the net.
The idea is to drop it only if it exists, because if not, I will have an error and my process stops.
I found this to find if the index exists:
select index_name
from user_indexes
where table_name = 'myTable'
and index_name='myIndexName'
But I don't know how to put it together with
DROP INDEX myIndexName
Don't check for existence. Try to drop, and capture the exception if necessary...
DECLARE
index_not_exists EXCEPTION;
PRAGMA EXCEPTION_INIT (index_not_exists, -1418);
BEGIN
EXECUTE IMMEDIATE 'drop index foo';
EXCEPTION
WHEN index_not_exists
THEN
NULL;
END;
/
DECLARE
COUNT_INDEXES INTEGER;
BEGIN
SELECT COUNT ( * )
INTO COUNT_INDEXES
FROM USER_INDEXES
WHERE INDEX_NAME = 'myIndexName';
-- Edited by UltraCommit, October 1st, 2019
-- Accepted answer has a race condition.
-- The index could have been dropped between the line that checks the count
-- and the execute immediate
IF COUNT_INDEXES > 0
THEN
EXECUTE IMMEDIATE 'DROP INDEX myIndexName';
END IF;
END;
/
In Oracle, you can't mix both DDL and DML. In order to do so, you need to work it around with the EXECUTE IMMEDIATE statement.
So, first check for the existence of the index.
Second, drop the index through the EXECUTE IMMEDIATE statement.
DECLARE v_Exists NUMBER;
BEGIN
v_Exists := 0;
SELECT 1 INTO v_Exists
FROM USER_INDEXES
WHERE TABLE_NAME LIKE 'myTable'
AND INDEX_NAME LIKE 'myIndexName'
IF v_Exists = 1 THEN
EXECUTE IMMEDIATE "DROP INDEX myIndexName"
ENDIF;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
This code is out the top of my head and you may need to fix it up a little, but this gives an idea.
Hope this helps! =)
I made a procedure so it can be called several times:
DELIMITER €€
DROP PROCEDURE IF EXISTS ClearIndex€€
CREATE PROCEDURE ClearIndex(IN var_index VARCHAR(255),IN var_table VARCHAR(255))
BEGIN
SET #temp = concat('DROP INDEX ', var_index, ' ON ', var_table);
PREPARE stm1 FROM #temp;
BEGIN
DECLARE CONTINUE HANDLER FOR 1091 SELECT concat('Index ', var_index,' did not exist in ',var_table,', but was handled') AS 'INFO';
EXECUTE stm1;
END;
END €€
DELIMITER ;
Now it can be called more than once:
CALL ClearIndex('employees_no_index','employees');
CALL ClearIndex('salaries_no_index','salaries');
CALL ClearIndex('titles_no_index','titles');
I hope this will help. It's a combination of all solution :)
By the way thanks for the help !
CREATE OR REPLACE PROCEDURE CLEAR_INDEX(INDEX_NAME IN VARCHAR2) AS
BEGIN
EXECUTE IMMEDIATE 'drop index ' || INDEX_NAME;
EXCEPTION
WHEN OTHERS THEN
NULL;
END CLEAR_INDEX;

Resources