I need to write a script with an embedded PL/SQL that opens a few cursors and loops through records fetched performing some actions.
All cursors have part of their WHERE a condition that look like this: AND table_name IN ('table_one', 'table_two' ... 'last_table_from_a_long_list')
Because the same list of tables is used everywhere I would prefer not to repeat myself but rather to declare the list of tables somewhere and reuse it in the each cursor definition. Ideally you would put those table names in a real table and everything would work OK; however that script will run in production and creating/dropping tables for this purpose is not an accepted practice. The same restriction would apply about creating a user defined type of table_name and another one as a TABLE OF table_name.
Just wondering if there are some PL/SQL tricks that would help me achieve the same thing. Repeating the long list of tables a dozen of times would make that code look quite bad. This said my script is an "one off" thing so maybe I am worrying too much. There is still the professional "how to" curiosity though.
Thank you in advance for your inputs.
Short answer: repeat the code.
Longer answer: not repeating the code is more work than just biting the bullet and dealing with a little less-than-perfectly elegant code.
Longest answer: if you really need to insist on doing this then look into using dynamically-created SQL - build your SELECT statement(s) out of strings, then either use the OPEN cursor FOR sqlString, FETCH cursor INTO..., and CLOSE cursor statements, or for extra karma and a pile of difficulty study up on the DBMS_SQL package - and may C͡͏ţ͘húl͞ḩ̸u҉͝ have mercy upon your soul.
Best of luck.
Couple of options you can try
EXECUTE IMMEDIATE
-- Cursor Example
sql_stmt := 'SELECT * FROM emp WHERE empno = :id';
EXECUTE IMMEDIATE sql_stmt INTO emp_rec USING emp_id;
Another option is DBMS_SQL
CREATE OR REPLACE PROCEDURE DYNSQL AS
cur integer;
rc integer;
BEGIN
cur := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(cur, 'CREATE TABLE X (Y DATE)', DBMS_SQL.NATIVE);
rc := DBMS_SQL.EXECUTE(cur);
DBMS_SQL.CLOSE_CURSOR(cur);
END;
You can google with these key words and you will find many examples.
Related
I have below query which I am trying to write in a procedure after begin clause. I dont want to use it as a cursor because of some dependency.
I want to make my db link dynamic instead of hardcoding it and for this reason i put my entire for loop in variable. If i take the variable out then my procedure is working fine. I dont want to change logic of my code while trying to make dblink dynamic.
But this part of loop is not working and throwing an error as
encounter the symbol end of the file when expecting one of the following:
PROCEDURE TMP_CHECK
IS
open CS for NESS_QUERY;
loop
fetch CS into REC;
exit when CS%notfound;
INSERT INTO TMP_Data(ID,NAME,ID_TST,CHK_DATE,VALUE,CHECK,SOURCE) VALUES
(IN_SEQ_NO,DB_NAME,DB_ID,REC.DAY_ID,REC.nb_ord,'ORDS','LEOSOFT');
COMMIT;
END LOOP;
CLOSE CS;
END LOOP;
END;
Dynamic SQL is hard because it turns compilation errors into runtime errors. It looks like your query has several compilation errors: duplicate table aliases, out-of-scope alias references, cross joins between the remote tables (unless that is deliberate, in which case yuck!). So the first thing to do is get the query running as straight SQL, only then make it dynamic.
Also don't include commented code in your template SQL. Things are already hard enough, why make them even harder by doing stuff like this?
ORDER BY
-- TE.market asc,
-- TE.entity asc,
TE.dayiid ASC)'
So, now we've got that out of the way let's look at the logic of what you're trying to do. We cannot drop dynamic segments of PL/SQL into a program. This just won't work ...
LQUERY='
FOR REC IN(
SELECT
... because you have not written a complete PL/SQL statement. But there is a way to do what you want: use a cursor variable. We can open a ref cursor for static and dynamic queries. Find out more.
The following is for illustrative purposes only: you haven't explained your business logic, so this is not necessarily the best way of doing things. But it should solve your immediate problem:
declare
....
l_order number;
l_dayiid number;
l_ety_id number;
rc sys_refcursor;
begin
...
FOR IIS_DB IN C_DB
LOOP
IN_DB_LINK:=LEO_DB.DATABASE_LINK;
IN_DAY:=LEO_DB.DAY_ID;
open rc for
'SELECT order,dayiid,ety_id
from ...
ORDER BY TE.dayiid ASC)';
loop
fetch rc into l_order, l_dayiid, l_ety_id;
exit when rc%notfound;
...
end loop;
close rc;
" PLS-00487: Invalid reference to variable 'REC'"
I think your problem is this:
fetch CS into REC;
You have defined REC as a string but clearly it should be a record type, which needs to match the projection of the query you're fetching. So you need to define something like this:
Type rec_t is record (
nb_ord number,
day_id number,
entity number
);
REC rec_t;
Now you can fetch a record into REC and reference its attributes.
Incidentally the nvl() you've written to supply NB_ORD is wrong. The first argument is the one you are testing for null: 500 will never be null so that's what you'll get for every row. You need to swap the parameters round.
i'am facing a problem while trying to make a conception for a school project.and i have two question.
1/
is it possible with oracle to do something like this : https://mariadb.com/kb/en/dynamic-columns/
2/
how can i make a modelisation for that with an MCD (method merise ).
thanks
Quote from your link: "It works by storing a set of columns in a blob and having a small set of functions to manipulate it.". I don't see an obvious reason why you couldn't write something similar for Oracle (or any other relational database).
Alternatively, take a look at EAV, which is a more traditional solution for dynamic columns.
Sorry, I'm not familiar with Merise.
1/ Oracle is not made for creating dynamic columns, but you can do it by using PL/SQL functions (but not while querying with SQL):
create or replace procedure add_table_column(table_name varchar2, column_name varchar2, column_type varchar2)
is
v_script varchar2(4000);
begin
v_script := 'alter table '||table_name||' add '||column_name||' '||column_type;
execute immediate v_script;
end;
/
You can call it this way:
begin
add_table_column('toto','test','number');
end;
/
2/ Merise method is not made for "dynamic objects" as you must begin with a complete data dictionary.
How is your object dynamic, and why do you have to add columns?
You can always get a solution to work by using "regular" tables and relationships.
It sounds like you should start by analyzing the technical implementation in mind. MERISE says to analyze your data model first and start to think about technical solutions after.
It may sound like a silly question, but I hope I'll make myself clear enough.
When talking about Spaghetti Code, the basis of it is the
usage of GOTOs. I had a peer that was used to say if I put a breakpoint at the end of the code and this breakpoint isn't reached everytime, something is wrong.
Nevertheless, is a common (and I'd say, a rule) to use EXIT WHEN
structures within Oracle packages (usually followed by a %NOTFOUND
test).
Taking for granted that using EXIT breaks the programming flow, isn't something that doesn't match between 1 and 2?
Is everyone programming in PL/SQL following a bad practice? Does PL/SQL don't follow this specific pattern for conditionals?
Is there any performance reason under Oracle's hood to use such statements?
Apologies if this question has been already asked, I couldn't find anything similar around.
Yes, many people are following a bad practice.
Bad Style
I agree with #Osy that OPEN/FETCH/CLOSE adds completely unnecessary code. I would go even further, and say that you should almost never use CURSOR.
First of all, you normally want to do as much as possible in plain SQL. If you need to use PL/SQL, use an implicit cursor. It will save you a line of code and will help you keep related logic closer together.
I'm a strong believer in keeping individual units of code as small as possible. At first glance, it seems like a CURSOR can help you do this. You can define your SQL up top in one place, and then do the PL/SQL looping later.
But in reality, that extra layer of indirection is almost never worth it. Sometimes a lot of logic is in SQL, and sometimes a lot of logic is in PL/SQL. But in practice, it rarely makes sense to put a lot of complex logic in both. Your code usually ends up looking like
one of these:
for records in (<simple SQL>) loop
<complex PL/SQL>
end loop;
or:
for records in
(
<complex SQL>
) loop
<simple PL/SQL>;
end loop;
Either way, one of your code sections will be very small. The complexity of separating those two sections of code is greater than the complexity of a larger, single section of code. (But that is obviously my opinion.)
Bad Performance
There are significant performance implications with using OPEN/FETCH/CLOSE. That method is much slower than using a cursor for loop or an implicit cursor.
The compiler can automatically use bulk collect in some for loops. But, to quote from the Oracle presentation "PL/SQL Performance—Debunking the Myths", page 122:
Don’t throw this chance away by using the open, fetch loop, close form
Here's a quick example:
--Sample data
create table t(a number, b number);
insert into t select level, level from dual connect by level <= 100000;
commit;
--OPEN/FETCH/CLOSE
--1.5 seconds
declare
cursor test_cur is
select a, b from t;
test_rec test_cur%rowtype;
counter number;
begin
open test_cur;
loop
fetch test_cur into test_rec;
exit when test_cur%notfound;
counter := counter + 1;
end loop;
close test_cur;
end;
/
--Implicit cursor
--0.2 seconds
declare
counter number;
begin
for test_rec in (select a, b from t) loop
counter := counter + 1;
end loop;
end;
/
It´is very recommendable to keep it simple your code, so I can tell you what PL/SQL guru says about it:
NOTE : In some cases is not recommendable use of CURSOR-FOR-LOOP. You can consider one more intelligent manner of choose single SELECT-INTO or BULK COLLECT according of your needs.
Source : On Cursor FOR Loops, Oracle Magazine By Steven Feuerstein
(Reference: Feuerstein, TOP TWENTY PL/SQL TIPS AND TECHNIQUES):
Loops
12. Take advantage of the cursor FOR loop.
The cursor FOR loop is
one of my favorite PL/SQL constructs. It leverages fully the tight and
effective integration of the procedural aspects of the language with
the power of the SQL database language. It reduces the volume of code
you need to write to fetch data from a cursor. It greatly lessens the
chance of introducing loop errors in your programming - and loops are
one of the more error-prone parts of a program. Does this loop sound
too good to be true? Well, it isn’t - it’s all true!
Suppose I need to update the bills for all pets staying in my pet
hotel, the Share-a-Din-Din Inn. The example below contains an
anonymous block that uses a cursor, occupancy_cur, to select the room
number and pet ID number for all occupants at the Inn. The procedure
update_bill adds any new changes to that pet’s room charges.
DECLARE
CURSOR occupancy_cur IS
SELECT pet_id, room_number
FROM occupancy
WHERE occupied_dt = SYSDATE;
occupancy_rec occupancy_cur%ROWTYPE;
BEGIN
OPEN occupancy_cur;
LOOP
FETCH occupancy_cur
INTO occupancy_rec;
EXIT WHEN occupancy_cur%NOTFOUND;
update_bill
(occupancy_rec.pet_id,
occupancy_rec.room_number);
END LOOP;
CLOSE occupancy_cur;
END;
This code leaves nothing to the imagination. In addition to defining
the cursor (line 2), you must explicitly declare the record for the
cursor (line 5), open the cursor (line 7), start up an infinite loop,
fetch a row from the cursor set into the record (line 9), check for an
end-ofdata condition with the cursor attribute (line 10), and finally
perform the update. When you are all done, you have to remember to
close the cursor (line 14). If I convert this PL/SQL block to use a
cursor FOR loop, then I all I have is:
DECLARE
CURSOR occupancy_cur IS
SELECT pet_id, room_number
FROM occupancy WHERE occupied_dt =
SYSDATE;
BEGIN
FOR occupancy_rec IN occupancy_cur
LOOP
update_bill (occupancy_rec.pet_id,
occupancy_rec.room_number);
END LOOP;
END;
Here you see the beautiful simplicity of the cursor FOR loop! Gone is
the declaration of the record. Gone are the OPEN, FETCH, and CLOSE
statements. Gone is need to check the %FOUND attribute. Gone are the
worries of getting everything right. Instead, you say to PL/SQL, in
effect:: ÒYou and I both know that I want each row and I want to dump
that row into a record that matches the cursor. Take care of that for
me, will you?" And PL/SQL does take care of it, just the way any
modern programming language integrated with SQL should.
Here's a piece of Oracle code I'm trying to adapt. I've abbreviated all the details:
declare
begin
loop
--do stuff to populate a global temporary table. I'll call it 'TempTable'
end loop;
end;
/
Select * from TempTable
Right now, this query runs fine provided I run it in two steps. First I run the program at the top, then I run the select * to get the results.
Is it possible to combine the two pieces so that I can populate the global temp table and retrieve the results all in one step?
Thanks in advance!
Well, for me it depends on how I would see the steps. You are doing a PL/SQL and SQL command. I would rather type in those into a file, and run them in one command (if that could called as a single step for you)...
Something like
file.sql
begin
loop
--do stuff to populate a global temporary table. I'll call it 'TempTable'
end loop;
end;
/
Select *
from TempTable
/
And run it as:
prompt> sqlplus /#db #file.sql
If you give us more details like how you populate the GTT, perhaps we might find a way to do it in a single step.
Yes, but it's not trivial.
create global temporary table my_gtt
( ... )
on commit preserve rows;
create or replace type my_gtt_rowtype as object
( [columns definition] )
/
create or replace type my_gtt_tabtype as table of my_gtt_rowtype
/
create or replace function pipe_rows_from_gtt
return my_gtt_tabtype
pipelined
is
pragma autonomous_transaction;
type rc_type is refcursor;
my_rc rc_type;
my_output_rec my_gtt_rectype := my_gtt_rectype ([nulls for each attribute]);
begin
delete from my_gtt;
insert into my_gtt ...
commit;
open my_rc for select * from my_gtt;
loop
fetch my_rc into my_output_rec.attribute1, my_output_rec.attribute1, etc;
exit when my_rc%notfound;
pipe_row (my_output_rec);
end loop;
close my_rc;
return;
end;
/
I don't know it the autonomous transaction pragma is required - but I suspect it is, otherwise it'll throw errors about functions performing DML.
We use code like this to have reporting engines which can't perform procedural logic build the global temporary tables they use (and reuse) in various subreports.
In oracle, an extra table to store intermediate results is very seldom needed. It might help to make things easier to understand. When you are able to write SQL to fill the intermediate table, you can certainly query the rows in a single step without having to waste time by filling a GTT. If you are using pl/sql to populate the GTT, see if this can be corrected to be pure SQL. That will almost certainly give you a performance benefit.
There have been a couple of hints that seem to have gotten me close here, but with some unique issues, I'm hoping this question is distinguishing enough to merit its own posting.
For starters here's what I have. I have an Oracle procedure that returns a standard REF CURSOR, and this REF CURSOR is passed back to my application. The REF CURSOR is a list of lookup IDs.
I then want to take this list and bring it to another data store and use it in a select statement. It will absolutely be possible to accomplish this by looping through the REF CURSOR, but I'm hoping to avoid that. I would much rather be able to write a SELECT...WHERE lookup_id IN result_ref_cursor OR SELECT...WHERE EXISTS...
First is this possible or should I just try a less than elegant solution? If it is possible, any hints as to where I should get started looking?
I'm relatively new to Oracle, but fairly experienced in RDBMs in general, so feel free to just through some links at me and I can study up. Much appreciated
Why kurosch didn't put his response as an "answer" I'll have no idea.
So, what you do is define a SQL type which describes one row of the output of the ref cursor, and also a SQL type which is a table of the previous. Then, you'll create a pipelined function which returns the rows returned by the ref cursor. This function can then be used in a standard SQL. I'm borrowing from Ask Tom on this one.
create or replace type myLookupId as object ( id int)
/
create or replace type myLookupIdTable as table of myLookupId
/
create or replace function f return myLookupIdTable PIPELINED is
l_data myLookupId;
l_id number;
p_cursor SYS_REFCURSOR;
begin
p_cursor := function_returning_ref_cursor();
loop
fetch p_cursor into l_id;
exit when p_cursor%notfound;
l_data := myLookupId( l_id );
pipe row (l_data);
end loop;
return;
end;
/
And now a sample query...
SELECT *
FROM SOME_TABLE
WHERE lookup_id in (SELECT ID FROM table(f));
Sorry if the code isn't exactly right, I don't have the DB to test right now.
There are several directions you could go with this, but I did a search on the specific solution you want and it seems like no-one has done it often enough to show up there. What you can do is search the oracle metalink - that is usually really good at finding obscure answers. (Though you do need a service agreement - just found out that mine expired :( )
Other possible solutions:
Create a link between the data stores so that you can do the select in the plsql directly
Create a function in Java that loops through it for you to create the string for the query. This will look a little more pretty at least.
Otherwise, REF CURSOR's need to go back and forth - I don't know how you can pipe the results of the REF CURSOR in one connection to the query in another without looping through it.