Reusing large SQL queries in stored procedures - plsql

I have a number of long SQL select queries( 150 lines+) that I want to use in a PL/SQL package. The package has procedures to execute the SQL queries and insert the results into a separate table, compare the SQL results to another table, delete rows etc
Its fairly easy to store the SQL results with:
INSERT into TABLE1
SELECT .... (150 line ugly select query goes here)
Problem is, I want to store the select SQL in a cursor/function/view/whatever-works so I don't have to paste the 150 lines query into each procedure where the SQL is used.
I can store the SQL as a cursor then loop through the cursor within a package procedure, fetching each row and eg inserting into my table. But this seems very inefficient considering my only motivation for using a cursor is reducing the amount of lines my package.
Is there a better way to call the SQL select query in different procedures without copying & pasting all 150 lines? If this was a script, I would store the SQL in a text file then just read the text file into a variable and pass the variable to sqlplus when needed. But I'm not very familiar with PL/SQL.
Code:
CREATE OR REPLACE PACKAGE BODY MyPackage
as
Cursor my_cursor
select (150+ lines goes here)
PROCEDURE PopulateTable
is
TYPE fetch_array IS TABLE OF my_cursor%ROWTYPE;
s_array fetch_array;
BEGIN
open my_cursor;
LOOP
FETCH tran_cursor BULK COLLECT INTO s_array;
FORALL counter in 1..s_array.COUNT
INSERT INTO my_table VALUES s_array(counter);
EXIT when s_array%NOTFOUND;
END LOOP;
close my_cursor;
COMMIT;
END PopulateTable;
END MyPackage;

I am not sure if this would be the best way to do, but what came to my mind, is a variable cursor. You could do that using SYS_REFCURSOR. You can build a function that contains your query, and returns ref curosr. In all your procedures, you can just call that function. This will save you writing 150+ lines query in every procedure. More important, it will limit your program to one copy of the query, and therefore easy to maintain.
The function that returns the ref cursor, could be something like this:
CREATE OR REPLACE FUNCTION my_ugly_query()
RETURN SYS_REFCURSOR
AS
my_cursor_ref SYS_REFCURSOR;
BEGIN
OPEN my_cursor_ref FOR
SELECT -- 150+ lines of query;
RETURN my_cursor_ref;
END;
This is how to use it:
CREATE OR REPLACE PACKAGE BODY MyPackage
as
PROCEDURE PopulateTable
IS
l_cur_refcur SYS_REFCURSOR;
s_array fetch_array;
BEGIN
l_cur_refcur := my_ugly_query();
LOOP
FETCH tran_cursor BULK COLLECT INTO s_array;
EXIT when s_array%NOTFOUND;
FORALL counter in 1..s_array.COUNT
INSERT INTO my_table VALUES s_array(counter);
END LOOP;
CLOSE my_cursor;
COMMIT;
END PopulateTable;
END MyPackage;

Create your cursor in the package specification rather than the package body. You may then refer to it from any package procedure/function using package_name.cursor_name

Related

How to correctly make a procedure in Pl/SQL in which I create a TABLE and use a CURSOR

The assignment I am trying to do is
"Create a procedure that places the names of all presidents who were born in one specific
state, in a temporary table. Display the contents of this table."
The procedure complies but when I try to invoke it, it gives me:
00000 - "table or view does not exist"
Error(8,5): PLS-00103: Encountered the symbol "CREATE" when expecting one of the following: begin function pragma procedure subtype type current cursor delete exists prior
I have been stuck for a while now. Does anybody know what I am doing wrong?
My code so far is:
CREATE OR REPLACE PROCEDURE stateofpresident(p_state president.state_born%TYPE)
AS
CURSOR c_state IS
SELECT *
FROM president;
BEGIN
execute immediate 'CREATE TABLE presidentFromState;
(
president_name VARCHAR2
)';
FOR r_state IN c_state LOOP
IF(p_state = r_state.state_born) THEN
execute immediate 'INSERT INTO presidentFromState VALUES(r_state.pres_name)';
commit;
END IF;
END LOOP;
execute immediate 'DROP TABLE presidentFromState';
END stateofpresident;
/
SET SERVEROUT ON
BEGIN
stateofpresident('VIRGINIA');
END;
/
SELECT *
FROM presidentFromState;
The immediate cause of your error is the semi-colon (;) at "presidentFromState;" At run fhat terminates the statement and the SQL interpreter at that point does not know what is want, the create syntax is invalid. The statement compiles because at compile time it is a properly formatted string. That is why dynamic SQL should be avoid if at all possible. Your script also has an additional error. Your last select will fail as the table presidentFromState ws not only created but also dropped in the procedure. Finally, just an FYI, the entire FOR cursor and the cursor itself is entirely unnecessary, the entire operation can be completed is one statement: Look into the structure
Insert into table_name(columns)
Select columns ...
Since this obviously an assignment or tutorial I'll leave the exact for your research.

PLSQL: No output displayed when using dynamic query inside Stored Procedure

I have been asked to create an SP which creates temporary table and insert some records.
I am preparing some sample code for the same as mentioned below but the output is not displayed.
create or replace procedure Test
is
stmt varchar2(1000);
stmt2 varchar2(1000);
begin
stmt := 'create global temporary table temp_1(id number(10))';
execute immediate stmt;
insert into temp_1(id) values (10);
execute immediate 'Select * from temp_1';
execute immediate 'Drop table temp_1';
commit;
end;
When i am executing the SP by (Exec Test) desired O/P is not displayed.
I am expecting O/P of "Select * from temp_1" to be displayed. But it is not happening. Please suggest where i am doing wrong.
But i am interesting in knowing why ( execute immediate 'Select * from temp_1';) do not yield any result
For two reasons. Firstly because as #a_horse_with_no_name said PL/SQL won't display the result of a query. But more importantly here, perhaps, the query is never actually executed. This behaviour is stated in the documentation:
If dynamic_sql_statement is a SELECT statement, and you omit both into_clause and bulk_collect_into_clause, then *execute_immediate_statement( never executes.
You would have to execute immediate into a variable, or more likely a collection if your real scenario has more than one row, and then process that data - iterating over the collection in the bulk case.
There is not really a reliable way to display anything from PL/SQL; you can use dbms_output but that's more suited for debugging than real output, and you usually have no guarantee that the client will be configured to show whatever you put into its buffer.
This is all rather academic since creating and dropping a GTT on the fly is not a good idea and there are better ways to accomplish whatever it is you're trying to do.
The block you showed shouldn't actually run at all; as you're creating temp_1 dynamically, the static SQL insert into temp_1 will error as that table does not yet exist when the block is compiled. The insert would have to be dynamic too. Any dynamic SQL is a bit of a warning sign you're maybe doing something wrong, though it is sometimes necessary; having to do everything dynamically suggests the whole approach needs a rethink, as does creating objects at runtime.

Casting a String to a Symbol in PLSQL

I've created an anonymous PLSQL block to test and I'm running into an issue with the formatting.
set serveroutput ON
BEGIN
FOR I IN (SELECT DISTINCT do.SUBOBJECT_NAME from dba_objects do WHERE do.object_name='MY_TABLE' AND do.OBJECT_TYPE='TABLE PARTITION') LOOP
dbms_output.put_line(I.subobject_name);
SELECT
t.field
INTO
some_var
FROM
MY_TABLE PARTITION(I.subobject_name) t;
END LOOP;
END;
However I get several compilation errors, which I believe are related to the fact that I.subobject_name is a string. I believe the PARTITION function wants an actual partition symbol(proper term for this?), but I can't give it in this loop.
Is there any kind of casting function that can perform what I'm looking for?
Partition IS NOT A FUNCTION. Partition is keyword
In your context your whole statement is static , thus you CANNOT pass partition name into it; partition name must be specified at compile time.
You can re-create your statement dynamically and then pass partition name in the loop -
a-la you are doing it. Just make sure you will concatenate string and not use bind variables, or your statement at run time won't be parsed and won't run.
Name for the symbol is table partition

compilation issue with execute immediate in Forall statement

Please help me in resolving below issue i am facing, i have to insert data into a table(table name genereted using variable value and table is created already) within FORALL..
Declare
TYPE dept_data_rec IS RECORD
(
Dept_no number,
Dept_name varchar2(100),
Dept_loc Varchar2(20)
);
TYPE nt_dept_data IS TABLE OF dept_data_rec ;
l_dept_data_nt nt_dept_data;
BEGIN
FORALL j IN 1..l_dept_data_nt.COUNT SAVE EXCEPTIONS
EXECUTE IMMEDIATE 'INSERT INTO '||l_get_dept_rec.dept_seq_no||'_Dept_Data VALUES '||
l_dept_data_nt(j);
COMMIT;
while compiling this code i am getting below error:
PLS-00306: wrong number or types of arguments in call to '||'
However when code using actual table name it works
FORALL j IN 1..l_dept_data_nt.COUNT SAVE EXCEPTIONS
INSERT INTO A1_dept_data VALUES
l_dept_data_nt(j);
COMMIT;
Oracle 10g -
In versions of Oracle prior to 11g, you can't use FORALL with EXECUTE IMMEDIATE, only with INSERT, UPDATE, or DELETE.
See http://docs.oracle.com/cd/B13789_01/appdev.101/b10807/13_elems021.htm
It's a special syntax
that reads like a FOR loop but isn't, and
is used by PL/SQL to perform bulked DML operations and only with the exact keyword, not with dynamic SQL or any other code.
Oracle 11g +
In 11g, the restriction on using EXECUTE IMMEDIATE was lifted. See http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/forall_statement.htm
However, the only variables allowed in the string are subscripted elements from a single array in the USING clause.
The documentation is unclear whether you can dynamically "change" the table per row using the FORALL syntax. Remember that the FORALL is used by PL/SQL to perform a bulk DML operation and that needs to go to one table for this to yield any performance benefit.
Best performance solution for the above problem
You should make two levels of arrays, the first defines which table and the second defines the data for that table.
Use an ordinary FOR loop over the table array and inside that loop use the special FORALL syntax to perform all the DML for the one table.

INSERT inside plsql procedure does not tell how many rows were inserted

i am trying to insert some rows and update some rows inside a pl/sql loop.
however all i get to see is the pl/sql procedure is successfully completed.
i do get to see dbmbs_ouput statements but not the output status of insert and/or update queries.
the serveroutput is set to on.
how do i get to see the status of insert and update rows(namely how many rows were inserted and updated)
In Oracle, the rowcount is not output automatically like it is in SQL Server.
You should do it explicitly:
BEGIN
INSERT
INTO mytable
SELECT …
FROM other_table;
DBMS_OUTPUT.put_line(SQL%ROWCOUNT);
END;

Resources