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.
Related
How do I perform a select on a rather simple view in oracle pl/sql using a stored procedure.
Lets say the view looks like this:
FirstName LastName
-------- -------
Bob Jones
James Kay
etc...
To me its should be so simple:
Procedure SuperSimple()
begin
select FirstName, LastName from SuperSimple
end
However I've been told that this will not work.
So I tried to use a PL/SQL cursor. Still scratching my head trying to figure out why I am using cursors. But it appears to be necessary in 11g.
Procedure AlphaPrime(Results OUT Ref CURSOR) IS
begin
OPEN Results for
select FirstName, LastName from SuperSimple;
end;
Now I was hoping this would work but I'm doing something like this with select statements and it appears to be not working.
Do I also need to add a fetch and another open and a close command to make this thing work? What is the idea behind all this? I've noticed that trying to find info on how to add a very simple select statemetn to a procedure appears to be missing from most documentation that I've read. Is there a reason for this like its too simple to add a select statement to a procedure as it would be better to add it to a view. Something along those lines.
The problem I'm having is I want to start out really simple and tac on a little bit more complexity to the sproc over time... where time ~ 1 to 2 hours. Can someone point me to some docs in Oracle PL/SQL that shows how to add a simple table or view. Also If the permissions for a specific view or table is not allowed does it just fail for that user or does it give an empty result set.
It is not clear from your question what are you intending to do with the query result inside your procedure. So here I make some examples with dbms_output which prints to screen out some message and data from your query. Probably you will replace it with your logic.
Let's have some view (actually it doesn't matter here whether you are querying view or table, but I would stick to your question)
create table some_simple_table(firstname varchar2(30), lastname varchar2(30));
/
create or replace view supersimple_view as select firstname, lastname, 'whatever1' whatever from some_simple_table;
/
The following code does select into variable, this will work only if query returns exactly one row.
create or replace procedure supersimple1 is
vfirstname supersimple_view.firstname%type;
vwhatever supersimple_view.whatever%type;
vsupersimple supersimple_view%rowtype;
begin
select firstname, whatever into vfirstname, vwhatever from supersimple_view;
dbms_output.put_line('I''m doing some logic with this'|| vwhatever );
select * into vsupersimple from supersimple_view;
dbms_output.put_line('I''m doing some logic with this'|| vsupersimple.firstname);
end;
/
Perhaps you can implement implicit cursor loop through results and do some logic.
create or replace procedure supersimple2 is
begin
for rec in (select * from supersimple_view)
loop
dbms_output.put_line('I''m doing some logic with this record '|| rec.firstname);
end loop;
end;
/
Another option is cursor (particularly in case when you will reuse the same select) loop through results and do some logic.
create or replace procedure supersimple3 is
cursor cur is (select * from supersimple_view);
vsupersimple cur%rowtype;
begin
open cur ;
loop
FETCH cur INTO vsupersimple;
EXIT WHEN cur%NOTFOUND;
dbms_output.put_line('I''m doing some logic with this record '|| vsupersimple.firstname);
end loop;
close cur;
end;
/
You can fetch result of your query to collection
create or replace procedure supersimple4 is
type supersimple_colt is table of supersimple_view%rowtype index by pls_integer;
vsupersimple_col supersimple_colt;
begin
select * bulk collect into vsupersimple_col from supersimple_view ;
for i in 1 .. vsupersimple_col.count
loop
dbms_output.put_line('I''m doing some logic with this record '|| vsupersimple_col(i).firstname);
end loop;
end;
/
Instead of PL/SQL type declared in supersimple4 you can create standalone database SQL types and used them to fetch results into. This aproach gives you various features like: possibility to query collection in select statement in table like fashion, converting it to xml by xmltype, etc.
I think I found the answer. For each column that is selected on, it needs a view or table column type, which is sort of like the list of parameters used for the final output. That way when you declare on it you can better know what you are getting, which sorta makes sense.
So if you have two tables or views which were used to generate the output columns, you would need both of those tables or views in your descriptive OUT variables to describe better what you are outputting in the final output result.
See this link.
I'm taking an educated guess with this next part as I'm just beginning to understand it:
This query should work. But if its not it may be due to insuffiecient priviledges. Try a table that you know you have access and select it in a procedure in debug mode. Then try a view.
Procedure AlphaPrime(Results OUT Ref CURSOR) IS
begin
OPEN Results for
select FirstName, LastName from SuperSimple;
end;
Also there is a possibility with Debug mode and your assigned user roles that you may have insufficient priviledges to debug all the objects in the view and they may not be accessible. Sometimes you can just hit the "Ignore" button in Toad to skip over debugging inside a stored procedure. Also you may have priveledges to view the results the object just not view its structure which may also give you insufficient priviledges errors. Again just ignore them to skip over those types of issues and see the results while in debug mode. If you don't debug this, then you should not see any errors and just get the results.
Scenario: we have flashback set up on certain tables in a Oracle database. Every now and then, we want to see what fields changed from one row to another. We can inspect visually of course but that is error-prone.
So I had the "brilliant" idea to try to step through the rows, store the current record into one record variable, and the prior record into another one. Then, field-by-field, compare each field, and if different, print out the field name and the values. Something like this:
DECLARE CURSOR myflash IS SELECT * FROM myflashtable;
OLDRECORD myflashtable%ROWTYPE;
NEWRECORD myflashtable%ROWTYPE;
dynamic_statement varchar2(4000);
cursor colnames is select * from all_tab_columns where table_name = 'myflashtable';
begin
if not myflash%ISOPEN then
open myflash;
end if;
fetch myflash into NEWRECORD;
while myflash%FOUND loop;
for columnnames in colnames loop
/* cobble together dynamic SQL along the lines of
"if oldrecord.column_name != newrecord.column_name
then print some information``....end if;"
*/
execute immediate dynamic_statement;
end loop;
OLDRECORD := NEWRECORD;
fetch myflash into NEWRECORD;
end loop;
end;
Naturally this didn't work. Initially it gave me "invalid SQL statement" and I added begin/end onto the dynamic SQL. When I tried running that version, it gave me an error because it doesn't know about the old/new records. When I run without doing the execute, but just dumping the generated SQL, it is stepping through all the columns on each of the records, so that part of the logic is working.
I'm quite sure there's a better way to do this, or perhaps to make it work. One thought was to do something like declaring old/new value variables, then using dynamic SQL to move the old/new record fields to each of those:
EXECUTE IMMEDIATE 'oldvalue := OLDRECORD.'||columnnames.column_name;
EXECUTE IMMEDIATE 'newvalue := NEWRECORD.'||columnnames.column_name;
IF oldvalue != newvalue then
/* print some stuff */
END IF:
but of course the trick is that the target variable would have to handle columns of a bunch of different types - char, date, etc. So there'd need to be variants of old/newvalue variables, and logic to handle that, and it was turning into not-so-much-fun.
Any suggestions for a more elegant way to do this? I've checked around the site and haven't had much like finding anything that quite seemed like what I'm trying to do.
You are on the right track. But it is quite some more programming work to do. Read the old and new table in a join linking it with the correct primary key and loop through it. You can use DMBS_SQL package to build a dynamic cursor and loop through the tables.
I'm quite new to PL/SQL, and am using Oracle SQL Developer to write a procedure which uses a sequence to generate a primary key for some existing data, to write into another DB.
The code in question is under NDA.. Essentially I have the following:
create or replace
PROCEDURE Generate_Data
(
output IN VARCHAR2
)
AS
-- Variables here --
CURSOR myCursor IS
SELECT data1, data2
FROM table;
CREATE SEQUENCE mySequence <-- error on this line
START WITH 0
INCREMENT BY 1;
BEGIN
LOOP
-- snip --
It raises the error PLS-00103, saying it encountered the symbol CREATE when expecting on of the following: begin, function, package, pragma, procedure, ...
I've been following the example at:
http://www.techonthenet.com/oracle/sequences.php
The reason you're getting this error is that you're trying to perform DDL, in this case creating a sequence, within PL/SQL. It is possible to do this, but you must use execute immediate.
As Alex says, you also wouldn't be able to do this in the declare section. It would look something like this:
begin
execute immediate 'CREATE SEQUENCE mySequence
START WITH 0
INCREMENT BY 1';
end;
However, as Padmarag also says, it's highly unlikely that you want to do this within PL/SQL. It would be more normal to create a sequence outside and then reference this later. More generally speaking, performing DDL inside a PL/SQL block is a bad idea; there should be no need for you to do it.
You don't mention what version of Oracle you're using. From 11g the ways in which you could access sequences got extended. If you're using 11g then you can access the sequence by creating a variable and assigning the next value in the sequence, .nextval, to this variable:
declare
l_seq number;
begin
loop
-- For each loop l_seq will be incremented.
l_seq := mysequence.nextval;
-- snip
end;
If you're before 11g you must (outside of DML) use a select statement in order to get the next value:
declare
l_seq number;
begin
loop
-- For each loop l_seq will be incremented.
select mysequence.nextval into l_seq from dual;
-- snip
end;
Please bear in mind that a sequence is meant to be a persistent object in the database. There is no need to drop and re-create it each time you want to use it. If you were to run your script, then re-run it the sequence would happily keep increasing the returned value.
Further Reading
About sequences
Using sequences
You can't create sequence in the DECLARE block of procedure. Move it after BEGIN. It's arguable if it makes sense, though. You probably need to create it outside your procedure in the first place.
Update
Actually, if you truly want it inside BEGIN/END use following:
EXECUTE IMMEDIATE 'CREATE SEQUENCE mySequence START WITH 0 INCREMENT BY 1';
You'd need to create the sequence before using it.
And in the PL/SQL code use
-- Variables here --1
v_seq_val number;
BEGIN
Select mySequence.nextval from dual into v_seq_val
In general SQL is for DDL(Data Definition Language) and PL/SQL is for DML(Data Manipulation Language) and logic.
If you wanted you could do Create from PL/SQL, but I think that's not what you want over here.
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.
I've heard that it's a good practice to define your records in PL/SQL by using the %ROWTYPE attribute. This saves typing and allows your package to continue functioning even when a column is added or deleted. (Correct me if I'm wrong!)
However, when I am fetching from a cursor that involves a join, I find that I have to fetch into a programmer-defined record that includes a (quite-possibly long) hand-written list of every column returned by the join.
So my question is:
Is it possible to fetch into nested records, or fetch into a list of records, or do something to avoid such an ugly kludge? Everything I've tried leads to an error about the record not matching what's being returned by the cursor.
Returning the result of a join using a cursor seems like such a common use-case to me that it's strange that nothing related to this comes up in a search.
Thank you.
You can user cursor%rowtype.
Sample:
declare
cursor c_c is
select emp.*, dept.* -- use aliasses if columns have same name
from emp
, dept; -- for sample no join condition
r_c c_c%rowtype;
begin
for r_c in c_c loop -- with for loop even the definition of r_c is not needed.
...
end loop;
end;
/
Why even bother with the cursor declaration?
This is equivalent.
begin
for r_c in (select emp.*, dept.* from emp, dept) loop
...
end loop;
end;
I see in your comment you mention this. But I see the explicit cursor syntax used so much, i think it's important to show.