Cursor loops; How to perform something at start/end of loop 1 time only? - sql

I've got the following in one of my Oracle procedures, I'm using it to generate XML
-- v_client_addons is set to '' to avoid null error
OPEN C_CLIENT_ADDONS;
LOOP
FETCH C_CLIENT_ADDONS INTO CLIENT_ADDONS;
EXIT WHEN C_CLIENT_ADDONS%NOTFOUND;
BEGIN
v_client_addons := v_client_addons || CLIENT_ADDONS.XML_DATA;
END;
END LOOP;
CLOSE C_CLIENT_ADDONS;
-- Do something later with v_client_addons
The loop should go through my cursor and pick out all of the XML values to display, such as :
<add-on name="some addon"/>
<add-on name="another addon"/>
What I would like to achieve is to have an XML start/end tag inside this loop, so I would have the following output
<addons>
<add-on name="some addon"/>
<add-on name="another addon"/>
</addons>
How can I do this without having the <addons> tag after every line? If there are no addons in the cursor (cursor is empty), then I would like to skip this part enitrely

check the length of v_client_addons. If it is greater than 0, you actually appened something. Then create you parent tag with its children, else just ignore it.

How about using SQL to generate the entire XML, instead of looping over a cursor?
SELECT XMLELEMENT("addons", XMLAGG(C.XML_DATA)) INTO v_client_addons
FROM CLIENT_ADDON_TABLE C;

Related

How to Insert data using cursor into new table having single column containing XML type data in Oracle?

I'm able to insert values into table 2 from table 1 and execute the PL/SQL procedure successfully but somehow the output is clunky. I don't know why?
Below is the code :
create table airports_2_xml
(
airport xmltype
);
declare
cursor insert_xml_cr is select * from airports_1_orcl;
begin
for i in insert_xml_cr
loop
insert into airports_2_xml values
(
xmlelement("OneAirport",
xmlelement("Rank", i.Rank) ||
xmlelement("airport",i.airport) ||
xmlelement("Location",i.Location) ||
xmlelement("Country", i.Country) ||
xmlelement("Code_iata",i.code_iata) ||
xmlelement("Code_icao", i.code_icao) ||
xmlelement("Total_Passenger",i.Total_Passenger) ||
xmlelement("Rank_change", i.Rank_change) ||
xmlelement("Percent_Change", i.Percent_change)
));
end loop;
end;
/
select * from airports_2_xml;
Output:
Why it is showing &lt ,&gt in the output ? And why am I unable to see the output fully?
Expected output:
<OneAirport>
<Rank>3</Rank>
<Airport>Dubai International</Airport>
<Location>Garhoud</Location>
<Country>United Arab Emirates</Country>
<Code_IATA>DXB</Code_IATA>
<Code_ICAO>OMDB</Code_ICAO>
<Total_passenger>88242099</Total_passenger>
<Rank_change>0</Rank_change>
<Percent_Change>5.5</Percent_Change>
</OneAirport>
The main issue is how you are constructnig the XML. You have an outer XMLElement for OneAirport, and the content of that element is a single string.
You are generating individual XMLElements from the cursor fields, but then you are concenating those together, which gives you a single string which still has the angle brackets you're expecting. So you're trying to do something like, simplified a bit:
select
xmlelement("OneAirport", '<Rank>1</Rank><airport>Hartsfield-Jackson</airport>')
from dual;
XMLELEMENT("ONEAIRPORT",'<RANK>1</RANK><AIRPORT>HARTSFIELD-JACKSON</AIRPORT>')
--------------------------------------------------------------------------------
<OneAirport><Rank>1</Rank><airport>Hartsfield-Jackson</airp
and by default XMLElement() escapes entities in the passed-in values, so the angle-brackets are being converted to 'safe' equivalents like <. If it didn't do that, or you told it not to with noentityescaping:
select xmlelement(noentityescaping "OneAirport", '<Rank>1</Rank><airport>Hartsfield-Jackson</airport>')
from dual;
XMLELEMENT(NOENTITYESCAPING"ONEAIRPORT",'<RANK>1</RANK><AIRPORT>HARTSFIELD-JACKS
--------------------------------------------------------------------------------
<OneAirport><Rank>1</Rank><airport>Hartsfield-Jackson</airport></OneAirport>
then that would appear to be better, but you still actually have a single element with a single string (with characters that are likely to cause problems down the line), rather than the XML structure you almost certainly intended.
A simple way to get an zctual structure is with XMLForest():
xmlelement("OneAirport",
xmlforest(i.Rank, i.airport, i.Location, i.Country, i.code_iata,
i.code_icao, i.Total_Passenger, i.Rank_change, i.Percent_change)
)
You don't need the cursor loop, or any PL/SQL; you can just do:
insert into airports_2_xml (airport)
select xmlelement("OneAirport",
xmlforest(i.Rank, i.airport, i.Location, i.Country, i.code_iata,
i.code_icao, i.Total_Passenger, i.Rank_change, i.Percent_change)
)
from airports_1_orcl i;
The secondary issue is the display. You'll see more data if you issue some formatting commands, such as:
set lines 120
set long 32767
set longchunk 32767
Those will tell your client to retrieve and show more of the long (XMLType here) data, rather the default 80 characters it's giving you now.
Once you are generating a nested XML structure you can use XMLSerialize() to display that more readable when you query your second table.
Try this below block :
declare
cursor insert_xml_cr is select * from airports_1_orcl;
v_airport_xml SYS.XMLTYPE;
begin
for i in insert_xml_cr
loop
SELECT XMLELEMENT ( "OneAirport",
XMLFOREST(i.Rank as "Rank"
,i.airport as "Airport"
,i.Location as "Location"
,i.Country as "Country"
,i.code_iata as "Code_iata"
,i.code_icao as "code_icao"
,i.Total_Passenger as "Total_Passenger"
, i.Rank_change as "Rank_change"
,i.Percent_change as "Percent_Change"
))
into v_airport_xml
FROM DUAL;
insert into airports_2_xml values (v_airport_xml);
end loop;
end;

how to exit the procedure if condition met in a loop PL SQL

Let's say I have a for loop
for i in array.first .. array.last loop
boolean := c(i) > d(i);
if boolean --is true then
exit the loop immediately and also exit the entire procedure
else if the boolean is never true til the end of the loop, exit the loop
and keep running other scripts in this procedure.
I know the 'EXIT' keyword needs to be inside of the loop in order to exit the loop if condition is met. And 'RETURN' needs to be outside of the loop, to exit the procedure.
But if I put 'RETURN' outside of the loop, then I think no matter what the result is from the loop, it will exit the entire procedure when the loop ends?
If you want to be didactic about it, you should use an EXIT to get out of the loop, then use a RETURN to exit from the procedure (duplicating any necessary tests), thus following the structured programming rule that "A procedure should only have a single entrance and a single exit". In practice 99.999% of programmers would just code the RETURN inside the body of the loop because A) it's clearer as to what's going on (you're not just getting out of the loop, you're returning from the procedure), and B) it's shorter. Do as you will. Best of luck.
define an exception, and when ever you want to exit raise and handle the exception as
create procedure exit_loop_example as
exit_loop_exception exception;
begin
/* previous code block */
begin
for i in 1..20 loop
raise exit_loop_exception;
end loop;
exception when
exit_loop_exception then
/* handle exit loop*/
null;
end;
/* next code block */
end;
The simple loop. It’s called simple for a reason: it starts simply with the LOOP keyword and ends with the END LOOP statement. The loop will terminate if you execute an EXIT, EXIT WHEN, or RETURN within the body of the loop (or if an exception is raised).
See Oracle Magazine
Following through with Tamás Kecskeméti's link, the only recommended way is to use a while loop with the desired condition specified in the beginning itself.
Below is and excerpt from the above link :
Code Listing 5: A WHILE loop with one exit
PROCEDURE display_multiple_years (
start_year_in IN PLS_INTEGER
, end_year_in IN PLS_INTEGER)
IS
l_current_year PLS_INTEGER := start_year_in;
BEGIN
WHILE ( l_current_year <= end_year_in
AND total_sales_for_year (l_current_year) > 0)
LOOP
display_total_sales_for_year (l_current_year);
l_current_year := l_current_year + 1;
END LOOP;
END display_multiple_years;

How to display the value of a variable used in a PL/SQL block in the log file of a batch file? And Arrays?

Basically I'm using a batch file to run a .sql file on Windows Task Scheduler. The batch file generates a log file that displays all the put_lines. I now want to also see the value assigned to the variable: v_chks_dm, but couldn't figure out a way to do it. Tried the get_line statement but failed... Does anyone know how to do it?
thanks!
This is what's in the batch file:
echo off
echo ****************************>>C:\output.log
sqlplus userid/password#csdpro #V:\CONDITION_TEST.sql>>C:\output.log
Here's the .sql file
declare
v_chks_dm number;
begin
Select /*+parallel (a,4)*/ count(distinct a.src_table) into v_chks_dm
from hcr_dm.hcr_dm_fact a;
dbms_output.put_line('v_chkt_dm value assigned');
-- dbms_output.get_line(v_chks_dm);
if.... then... else.... end if;
end;
One more question... what if the variable is an array? I have something like this, but got an error says ORA-06533: Subscript beyond count. The number of values in the array usually varies from 0 to 10, but could be more. Thanks!
declare
type v_chks_array is varray(10) of varchar2(50);
arrSRCs v_chks_array;
begin
arrSRCs :=v_chks_array();
arrSRCs.EXTEND(10);
Select /*+parallel (a,4)*/ distinct a.src_table BULK collect into arrSRCs
from hcr_dm.hcr_dm_fact a;
dbms_output.put_line(arrSRCs(10));
end;
dbms_output.put_line('v_chkt_dm value = ' || v_chkt_dm);
or, better
dbms_output.put_line('v_chkt_dm value = ' || to_char(v_chkt_dm, '<number format>'));
You can choose appropriate number formats in documentation.

Cursor error in SQL

I'm trying to implement a cursor, & after solving many errors I've finally come to a point where it runs, but it goes into infinite loop...
I've put the image of table below as image.
Aim of cursor: to calculate bowling average & store in 'bowling_avg' column.
here's the cursor code :
DECLARE
CURSOR BOWL_AVG IS SELECT SID,MATCHES,BOWLING_AVG,WICKETS
FROM BOWLING_STATS ;
NSID BOWLING_STATS.SID%TYPE;
NMATCHES BOWLING_STATS.MATCHES%TYPE;
NBOWLING_AVG BOWLING_STATS.BOWLING_AVG%TYPE;
NWICKETS BOWLING_STATS.WICKETS%TYPE;
BEGIN
OPEN BOWL_AVG;
IF BOWL_AVG%ISOPEN THEN
LOOP
FETCH BOWL_AVG INTO NSID,NMATCHES,NBOWLING_AVG,NWICKETS;
EXIT WHEN BOWL_AVG%NOTFOUND;
IF BOWL_AVG%FOUND THEN
LOOP
UPDATE BOWLING_STATS SET BOWLING_AVG=NWICKETS/NMATCHES WHERE SID=NSID ;
EXIT WHEN BOWL_AVG%NOTFOUND;
END LOOP;
END IF;
END LOOP;
ELSE
DBMS_OUTPUT.PUT_LINE('UNABLE TO OPEN CURSOR');
END IF;
CLOSE BOWL_AVG;
END;
I'm running this in oracle database 10g.
I ask for assistance in finding the error.
Thanks in advance.
Adding whitespace to your code makes it clearer what you're doing:
declare
cursor bowl_avg is
select sid, matches, bowling_avg, wickets
from bowling_stats;
nsid bowling_stats.sid%type;
nmatches bowling_stats.matches%type;
nbowling_avg bowling_stats.bowling_avg%type;
nwickets bowling_stats.wickets%type;
begin
-- 1. Open Cursor
open bowl_avg;
-- 2. Check if Cursor is open
if bowl_avg%isopen then
-- 3. Loop
loop
-- 4. Get record
fetch bowl_avg into nsid, nmatches, nbowling_avg, nwickets;
-- 5. Exit if no records left
exit when bowl_avg%notfound;
-- 6. If there is a record
if bowl_avg%found then
-- 7. Loop
loop
update bowling_stats
set bowling_avg = nwickets / nmatches
where sid = nsid;
-- 8. Exit if there is no record.
exit when bowl_avg%notfound;
end loop;
end if;
end loop;
else
dbms_output.put_line('unable to open cursor');
end if;
close bowl_avg;
end;
/
There are a number of contradictions in there.
In 1 and 2 you're opening a cursor and then checking if there is an open cursor. A error will be raised if the cursor didn't open so you can ignore this step.
In 5 and 6 you exit if you can't fetch a new record then check if you have a record. This is a contradiction so stage 6 will (almost) always evaluate to true.
in 7 and 8 you loop, exiting when you don't have a record. As you've just checked (twice) that you do in fact have a record you'll never exit this loop.
If you insist on doing this with cursors then you can remove most of your code and it should work fine:
declare
cursor bowl_avg is
select sid, matches, bowling_avg, wickets
from bowling_stats;
nsid bowling_stats.sid%type;
nmatches bowling_stats.matches%type;
nbowling_avg bowling_stats.bowling_avg%type;
nwickets bowling_stats.wickets%type;
begin
-- 1. Open Cursor
open bowl_avg;
-- 2. Loop
loop
-- 3. Get record
fetch bowl_avg into nsid, nmatches, nbowling_avg, nwickets;
-- 4. Exit loop if there is no record.
exit when bowl_avg%notfound;
-- 5. Perform UPDATE statement.
update bowling_stats
set bowling_avg = nwickets / nmatches
where sid = nsid;
end loop;
close bowl_avg;
end;
/
As always a single UPDATE statement without using loops, cursors or PL/SQL will be significantly more effective.

Skip a loop iteration with Firebird 2.5

I need to skip a While...Do loop iteration inside a stored procedure like this
While (v_counter <= :v_total) do begin
If (<condition>) then continue;
...
end
However CONTINUE won't be available until Firebird 3.0. Is there a work a round for this?
If you want to skip an iteration through a loop without CONTINUE, then just use the inverse of the continue-condition for the rest of the block:
While (v_counter <= :v_total) do begin
If (NOT <condition>) then
BEGIN
...
END
end