Cursor For Loop how to print message when not found - oracle

OK so I have a cursor for loop that I want to print an error message when the select statement doesn't find a course that the user has inputted. But the problem is that cursor for loops automatically exits when the select statement fails, so my else statement never executes. How do I print a message saying that they course they are looking for doesn't exist. Note switching to a cursor fetch is not an option. For example id the user enters a course that exists it prints all relevant information. When the user inputs a course with no prerequisite it prints a proper message, but if the user inputs a course that doesn't exists, nothing gets printed.
DECLARE
course_name VARCHAR2(40) := '&course_input';
TYPE course_r IS RECORD(
course_description course.description%TYPE,
cost course.cost%TYPE,
prerequisite course.prerequisite%TYPE,
prerequisite_cost course.cost%TYPE
);
course_rec course_r;
CURSOR course_cursor IS
SELECT a.description, a.cost, a.prerequisite, b.cost AS preq_cost
FROM COURSE a
LEFT JOIN COURSE b ON a.prerequisite = b.course_no
WHERE UPPER(a.description) LIKE '%'||'&course_input'||'%';
BEGIN
FOR record IN course_cursor
LOOP
course_rec.course_description := record.description;
course_rec.cost := record.cost;
course_rec.prerequisite := record.prerequisite;
course_rec.prerequisite_cost := record.preq_cost;
IF course_rec.prerequisite IS NULL THEN
DBMS_OUTPUT.PUT_LINE('There is NO prerequisite course for any that starts on ' || course_name || '. Try again');
ELSIF course_rec.prerequisite IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE('Course: ' || course_rec.course_description);
DBMS_OUTPUT.PUT_LINE('Cost: ' || course_rec.cost);
DBMS_OUTPUT.PUT_LINE('Prerequisite: ' || course_rec.prerequisite);
DBMS_OUTPUT.PUT_LINE('Prerequisite Cost: ' || course_rec.prerequisite_cost);
DBMS_OUTPUT.PUT_LINE('=================================================');
ELSE
DBMS_OUTPUT.PUT_LINE('There is NO VALID course that starts on '||course_name||'. Try again.');
END IF;
END LOOP;
END;
/

You can do this by having a variable which only gets set inside the loop. Then you can check that variable after the loop has completed to see if it was set, and decide if you need to do additional work.
Something like:
DECLARE
course_name VARCHAR2(40) := '&course_input';
v_rows_present BOOLEAN := FALSE;
BEGIN
FOR course_rec IN (SELECT a.description,
a.cost,
a.prerequisite,
b.cost AS preq_cost
FROM course a
LEFT JOIN course b
ON a.prerequisite = b.course_no
WHERE upper(a.description) LIKE '%' || course_name || '%')
LOOP
v_rows_present := TRUE;
IF course_rec.prerequisite IS NULL
THEN
dbms_output.put_line('There is NO prerequisite course for any that starts on ' || course_name || '. Try again');
ELSE
dbms_output.put_line('Course: ' || course_rec.course_description);
dbms_output.put_line('Cost: ' || course_rec.cost);
dbms_output.put_line('Prerequisite: ' || course_rec.prerequisite);
dbms_output.put_line('Prerequisite Cost: ' || course_rec.prerequisite_cost);
dbms_output.put_line('=================================================');
END IF;
END LOOP;
IF NOT v_rows_present
THEN
dbms_output.put_line('There is NO VALID course that starts on ' || course_name || '. Try again.');
END IF;
END;
/
N.B. I've updated your code as you appear to have misapprehended how to use a cursor for loop.
Cursor-for-loops create their own record variable implicitly, so you don't need to declare one yourself.
You also don't need to declare a cursor explicitly either - that can be done as part of the cursor-for-loop statement.
You don't need to populate a new record with the same values from the cursor-for-loop record in order to use the values (as long as you're using them within the cursor-for-loop, of course!)

You could declare a counter, as say, PLS_INTEGER, initialize it to 0, then increment it inside the loop. After the loop, you can check the value and if it's 0, you know no rows were returned.

THE CURSOR FOR LOOP doesn't execute if there are no such courses in the courses table.So, check if a row exists before entering the loop.
Two other things to note:
LIKE '%'||'&course_input'||'%' is not required in the where clause
as the same variable is already passed from user input and assigned
in the declare section.Simply use LIKE '%' || course_name || '%'
RECORD is a PL/SQL reserved word and shouldn't be used as a loop
index variable, i've changed it to rec.
DECLARE
course_name VARCHAR2(40) := '&course_input';
TYPE course_r IS RECORD ( course_description course.description%TYPE,
cost course.cost%TYPE,
prerequisite course.prerequisite%TYPE,
prerequisite_cost course.cost%TYPE );
course_rec course_r;
cur_count NUMBER;
CURSOR course_cursor IS SELECT a.description,
a.cost,
a.prerequisite,
b.cost AS preq_cost
FROM course a
LEFT JOIN course b ON a.prerequisite = b.course_no
WHERE upper(a.description) LIKE '%' || course_name || '%';
BEGIN
SELECT COUNT(*)
INTO cur_count
FROM course a
WHERE upper(a.description) LIKE '%' || course_name || '%';
IF
cur_count > 0
THEN
FOR rec IN course_cursor LOOP
course_rec.course_description := rec.description;
course_rec.cost := rec.cost;
course_rec.prerequisite := rec.prerequisite;
course_rec.prerequisite_cost := rec.preq_cost;
IF
course_rec.prerequisite IS NULL
THEN
dbms_output.put_line('There is NO prerequisite course for any that starts on ' ||
course_name || '. Try again');
ELSE
dbms_output.put_line('Course: ' || course_rec.course_description);
dbms_output.put_line('Cost: ' || course_rec.cost);
dbms_output.put_line('Prerequisite: ' || course_rec.prerequisite);
dbms_output.put_line('Prerequisite Cost: ' || course_rec.prerequisite_cost);
dbms_output.put_line('=================================================');
END IF;
END LOOP;
ELSE
dbms_output.put_line('There is NO VALID course that starts on ' || course_name || '. Try again.'
);
END IF;
END;
/

Related

Oracle pl/sql Array

let's see if somebody can help me, I need to delete rows from different tables and I did think to do it using an array so i wrote this :
DECLARE
TYPE mytype_a IS TABLE OF VARCHAR2(32) INDEX BY BINARY_INTEGER;
mytype mytype_a;
BEGIN
mytype(mytype.count + 1) := 'MYTABLE';
FOR i IN 1 .. mytype.count
LOOP
DELETE mytype(i) WHERE valid = 'N';
END LOOP;
END;
Trying to run this piece of code using sqldeveloper I get the ORA-00933 command not properly ended, if I put directly the table name it works, what am I doing wrong?
Thank you.
Thank you very much guys, it works perfectly.
This is not the correct approach. You have to use Dynamic SQL for this -
DECLARE
type mytype_a is table of varchar2(32) index by binary_integer;
mytype mytype_a;
stmt varchar(500) := NULL;
BEGIN
mytype (mytype.count + 1) := 'MYTABLE';
for i in 1..mytype.count loop
stmt := 'DELETE FROM ' || mytype(i) || ' where valid =''N''';
EXECUTE IMMEDIATE stmt;
end loop;
END;
You would need to use dynamic SQL, concatenating the table name from the collection into the statement, inside your loop:
execute immediate 'DELETE FROM ' || mytype(i) || ' where valid = ''N''';
Or you can put the statement into a variable so you can display it for debugging purposes, and then execute that, optionally with a bind variable for the valid value:
stmt := 'DELETE FROM ' || mytype(i) || ' where valid = :valid';
dbms_output.put_line(stmt);
execute immediate stmt using 'N';
dbms_output.put_line('Deleted ' || sql%rowcount || ' row(s)');
... which I've made also display how many rows were deleted from each table. Note though that you shoudln't rely on the caller being able to see anything printed with dbms_output - it's up to the client whether it shows it.
The whole anonymous block would then be:
DECLARE
type mytype_a is table of varchar2(32) index by binary_integer;
mytype mytype_a;
stmt varchar2(4000);
BEGIN
mytype (mytype.count + 1) := 'MYTABLE';
for i in 1..mytype.count loop
stmt := 'DELETE FROM ' || mytype(i) || ' where valid = :valid';
dbms_output.put_line(stmt);
execute immediate stmt using 'N';
dbms_output.put_line('Deleted ' || sql%rowcount || ' row(s)');
end loop;
END;
/
You could use a built-in collection type to simplify it even further.
db<>fiddle showing some options.
Hopefully this doesn't apply, but if you might have any tables with quoted identifiers then you would need to add quotes in the dynamic statement, e.g.:
stmt := 'DELETE FROM "' || mytype(i) || '" where valid = :valid';
... and make sure the table name values in your collection exactly match the names as they appear in the data dictionary (user_tables.table_name).

PL/SQL query error that I am running into. I have no clue what could be wrong

I'm trying to create a pl/sql query that fetches certain data and from tables and outputs the data. Here is what I have tried but I keep getting an error and I cannot begin to see where the problem is.
SET SERVEROUTPUT ON
DECLARE
TEMP_CUSTNAME CUSTOMER.FIRST_NAME%TYPE;
TEMP_CUSTSURNAME CUSTOMER.SURNAME%TYPE;
TEMP_COINPUR COIN.PRODUCT%TYPE;
TEMP_CPRICE COIN.PRICE%TYPE;
TEMP_DNOTES COIN_DELIVERY.DELIVERY_NOTES%TYPE;
CURSOR CURSOR1 IS
SELECT C.FIRST_NAME,C.SURNAME FROM CUSTOMER C, COIN.PRODUCT, COIN.PRICE, COIN_DELIVERY.DELIVERY_NOTES
WHERE COIN.PRICE > 8000;
BEGIN
OPEN CURSOR1;
LOOP
FETCH CURSOR1 INTO TEMP_CUSTNAME, TEMP_CUSTSURNAME, TEMP_COINPUR, TEMP_CPRICE, TEMP_DNOTES;
EXIT WHEN CURSOR1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE ('CUSTOMER: ' || TEMP_CUSTNAME || ',' ||TEMP_CUSTSURNAME);
DBMS_OUTPUT.PUT_LINE ('COIN: ' || TEMP_COINPUR || );
DBMS_OUTPUT.PUT_LINE ('PRICE: ' || TEMP_CPRICE || );
DBMS_OUTPUT.PUT_LINE ('NOTES: ' || TEMP_DNOTES || );
DBMS_OUTPUT.PUT_LINE ('------------------------------------' );
END LOOP;
CLOSE CURSOR1;
END;
It would be helpful if you've provided us with the error you're facing.
So far I see number of fields in the cursor (2) is not the same as number of variables being fetched into (5).
In other words, you need to add more columns here:
SELECT C.FIRST_NAME,C.SURNAME FROM CUSTOMER C, COIN.PRODUCT, COIN.PRICE,
so there will be enough data to fetch to this variables here:
FETCH CURSOR1 INTO TEMP_CUSTNAME, TEMP_CUSTSURNAME, TEMP_COINPUR, TEMP_CPRICE, TEMP_DNOTES;
Or, probably, you need to reduce number of variable you're fetching into like this. It depends on result you're trying to achieve
FETCH CURSOR1 INTO TEMP_CUSTNAME, TEMP_CUSTSURNAME;
UPD. wrong concatenation. Based on the error message from comments I see now there are problems here too. Remove trailing "||" in lines below
DBMS_OUTPUT.PUT_LINE ('COIN: ' || TEMP_COINPUR || );
DBMS_OUTPUT.PUT_LINE ('PRICE: ' || TEMP_CPRICE || );
DBMS_OUTPUT.PUT_LINE ('NOTES: ' || TEMP_DNOTES || );

How do I fix my procedure country_demographics?

CREATE OR REPLACE PROCEDURE country_demographics
(p_country_name IN countries.country_name%TYPE,
p_country_demo_rec OUT ed_type)
IS
TYPE ed_type IS RECORD (
c_name countries.country_name%TYPE,
c_location countries.location%TYPE,
c_capitol countries.capitol%TYPE,
c_population countries.population%TYPE,
c_airports countries.airports%TYPE,
c_climate countries.climate%TYPE);
BEGIN
SELECT country_name, location, capitol, population, airports, climate
INTO ed_type.c_name, ed_type.c_location, ed_type.c_capitol, ed_type.population, ed_type.airports, ed_type.climate
FROM countries;
DBMS_OUTPUT.PUT_LINE('Country Name:' || v_country_demo_rec.country_name ||
'Location:' || v_country_demo_rec.location ||
'Capitol:' || v_country_demo_rec.capitol ||
'Population:' || v_country_demo_rec.population ||
'Airports:' || v_country_demo_rec.airports ||
'Climate:' || v_country_demo_rec.climate );
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20201, 'This country does not exist.');
END IF;
END;
The problem is asking me to create a procedure called country_demograhics. Pass the country_name as an IN parameter. Display CONTRY_NAME, LOCATION, CAPITOL, POPULATION, AIRPORTS, CLIMATE. Use a user-defined record structure for the INTO clause of your select statement. Raise an exception if the country does not exist.
Now here is a copy of my code, that keeps coming back with an error of:
Error at line 0: PL/SQL: Compilation unit analysis terminated.
That error should be the second error which tells you, it will not look any further. There should be another error too. I guess that ed_type doesn't exist outside of the procedure so it can not have an ed_type as OUT parameter. ed_type isn't known outside.
First thing - Look you used the different variable in declaring(p_country_demo_rec ) and begin(v_country_demo_rec) part. I think that might be one mistake.
Try following script:- it may help you.
CREATE OR REPLACE PROCEDURE COUNTRY_DEMOGRAPHICS
IS
TYPE ED_TYPE IS TABLE OF countries%ROWTYPE;
p_country_demo_rec ED_TYPE;
BEGIN
SELECT * BULK COLLECT INTO p_country_demo_rec FROM countries;
FOR i IN p_country_demo_rec.FIRST..p_country_demo_rec.LAST
LOOP
DBMS_OUTPUT.PUT_LINE('Country Name:'||p_country_demo_rec(i).country_name ||
'Location:' || p_country_demo_rec(i).location ||
'Capitol:' || p_country_demo_rec(i).capitol ||
'Population:' || p_country_demo_rec(i).population ||
'Airports:' || p_country_demo_rec(i).airports ||
'Climate:' || p_country_demo_rec(i).climate );
END LOOP;
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20201, 'This country does not exist.');
END IF;
END;
/
EXECUTE COUNTRY_DEMOGRAPHICS;
Note:- You can use the one parameter(IN parameter) in a procedure to get the specific country demographics data and that parameter use in select statement for filter out the specific country.
Example:
CREATE OR REPLACE PROCEDURE COUNTRY_DEMOGRAPHICS(p_country_name IN varchar2)
Select statement looks like:
SELECT * BULK COLLECT INTO p_country_demo_rec FROM countries where
country_name = ||p_country_name;
Execute part:
EXECUTE COUNTRY_DEMOGRAPHICS(p_country_name);

how to display error message for oracle nested cursor loops

I have a project which creates a procedure and takes Date parameter in the argument. Upon execution of the procedure, it displays the Order Id and Order details in the Order Date column using nested cursor loop. I am also trying to display an error message for the date which is not available in my order tables.
The code to create procedure is as
create or replace procedure a05_order_details_by_date(p_order_date in date)
as
v_msg varchar2(400);
v_order_id ppl_order_headers.order_id%type;
v_order_date ppl_order_headers.order_date%type := p_order_date;
cursor cur_orders is
select order_id, order_date
from ppl_order_headers
where extract(month from order_date) = extract(month from v_order_date) and
extract(year from order_date) = extract(year from v_order_date) ;
cursor cur_order_details is
select ppl_order_details.plant_id, ppl_order_details.quantity, ppl_order_details.price, sum(ppl_order_details.quantity*ppl_order_details.price) as Extcost
from ppl_order_details
join ppl_order_headers on ppl_order_details.order_id = ppl_order_headers.order_id
where ppl_order_headers.order_id = v_order_id
group by ppl_order_details.plant_id, ppl_order_details.quantity, ppl_order_details.price;
begin
<< order_loop >>
for rec_orders in cur_orders
loop
case
when (extract(month from v_order_date)) != (extract(month from rec_orders.order_date))
and
(extract(year from v_order_date))!= (extract(year from rec_orders.order_date)) then
pr.pr('There are no orders for the requested month: ' || to_char(v_order_date, 'Month YYYY'));
else
pr.pr('Order ID' || ' ' || 'Order Date');
v_order_id := rec_orders.order_id;
v_msg := rpad(v_order_id, 7) || ' ' || rec_orders.order_date;
pr.pr(v_msg);
<< order_details >>
for rec_order_details in cur_order_details
loop
pr.pr(' ' || ' Plant ID' || ' ' || 'Quantity' || ' ' || 'Price' || ' ' || 'ExtCost' );
v_msg := ' ' || rec_order_details.plant_id || ' ' || rec_order_details.quantity ||
' ' || rec_order_details.price || ' ' || rec_order_details.Extcost;
pr.pr(v_msg);
end loop;
end case;
end loop;
end;
/
The result is fine when the date is given within the dataset. but when I try to run the procedure with Future date or date which is not in dataset, it is supposed to show an error message. But instead, it just shows "Anonymous Block completed".
You're looping over orders where the order date year/month matches your argument year/month; so your loop only contains matching data. But then inside that loop you say:
case
when (extract(month from v_order_date)) != (extract(month from rec_orders.order_date))
and
(extract(year from v_order_date))!= (extract(year from rec_orders.order_date)) then
Firstly you probably meant that to be 'or' not 'and', otherwise the same month in a different year would still be valid. But much more importantly that case condition can never be true. You're inside a loop which already dictates that the month and year for this record must match your date argument, because of the cursor's where clause.
So this case statement is redundant. If any data is found by the cursor then you will always go into the 'else' clause. If no data is found then you won't come into the cursor loop at all (as Tony Andrews pointed out), so the case isn't even evaluated.
You could generate your message by counting the number of records found as you go around the loop, or by setting a boolean variable; and then checking that state after the loop:
...
-- initial state is that we haven't seen any matching records
v_record_found boolean := false;
begin
<< order_loop >>
for rec_orders in cur_orders
loop
-- we have seen records matching the argument
v_record_found := true;
pr.pr('Order ID' || ' ' || 'Order Date');
v_order_id := rec_orders.order_id;
...
<< order_details >>
for rec_order_details in cur_order_details
loop
pr.pr(...)
...
pr.pr(v_msg);
end loop;
end loop;
-- was the flag changed inside the loop?
if not v_record_found then
pr.pr('There are no orders for the requested month: '
|| to_char(v_order_date, 'Month YYYY'));
end if;
end;
If there are no matching records you don't go into the loop, and the flag is never changed to true. But if there are matching records it is set to true, and the 'no orders' messages isn't shown.

Insertion from cursor failing due to data error, How to find the problematic data?

I have cursor which is selecting data and it is running quite fine
CURSOR Crs_c1 IS
SELECT a.GLOBAL_ACCOUNT_CODE,
a.LOCAL_ACCOUNT_CODE,
a.LOCAL_ACCOUNT_NAME,
c.GLOBAL_ACCOUNT_TYPE t_conto,
ltrim(rtrim(a.GLOBAL_ACCOUNT_CODE))||ltrim(rtrim(a.LOCAL_ACCOUNT_CODE)) GLOBAL_LOCAL
FROM V_LOCAL_ACCOUNTS#GDW_LIVE a,V_GLOBAL_ACCOUNTS#GDW_LIVE c --V_LOCAL_ACCOUNTS#GDWPP_ANY a,V_GLOBAL_ACCOUNTS#GDWPP_ANY c
WHERE a.enabled_flag = 'Y'
AND a.GLOBAL_ACCOUNT_CODE=c.GLOBAL_ACCOUNT_CODE
AND not exists (SELECT 1
FROM conto_gdw b -- considero solo i conti nuovi
WHERE a.LOCAL_ACCOUNT_CODE = b.c_conto_local_gdw
AND a.GLOBAL_ACCOUNT_CODE=b.C_CONTO_GLOBAL_GDW);
But at the time of insertion the code fails....
INSERT INTO conto_gdw
(C_CONTO_LOCAL_GDW,
S_CONTO_LOCAL_GDW,
C_CONTO_GLOBAL_GDW,
T_CONTO,
D_INIZIO,
GLOBAL_LOCAL)
VALUES (Rec_Crs_c1.LOCAL_ACCOUNT_CODE,
nvl(Rec_Crs_c1.LOCAL_ACCOUNT_NAME,'.'),
Rec_Crs_c1.GLOBAL_ACCOUNT_CODE,
Rec_Crs_c1.t_conto,
(v_anno_in * 100) + v_mese_in,
Rec_Crs_c1.GLOBAL_LOCAL);
May be this problem is occuring because of a single tuple (dataset). How can I find that culprit data?
I have checked all the data from cursor selection by taking it in a excel sheet, I could not find anything manually. Is there any way ORACLE would return me the failed data?
You need to have a proper exception handling while inserting data .Provide the output of the pl sql block .After than we can help you.
DECLARE
CURSOR Crs_c1
IS
SELECT a.GLOBAL_ACCOUNT_CODE,
a.LOCAL_ACCOUNT_CODE,
a.LOCAL_ACCOUNT_NAME,
c.GLOBAL_ACCOUNT_TYPE t_conto,
LTRIM (RTRIM (a.GLOBAL_ACCOUNT_CODE))
|| LTRIM (RTRIM (a.LOCAL_ACCOUNT_CODE))
GLOBAL_LOCAL
FROM V_LOCAL_ACCOUNTS#GDW_LIVE a, V_GLOBAL_ACCOUNTS#GDW_LIVE c
WHERE a.enabled_flag = 'Y'
AND a.GLOBAL_ACCOUNT_CODE = c.GLOBAL_ACCOUNT_CODE
AND NOT EXISTS
(SELECT 1
FROM conto_gdw b -- considero solo i conti nuovi
WHERE a.LOCAL_ACCOUNT_CODE = b.c_conto_local_gdw
AND a.GLOBAL_ACCOUNT_CODE =
b.C_CONTO_GLOBAL_GDW);
Rec_Crs_c1 Crs_c1%ROWTYPE;
BEGIN
OPEN Crs_c1;
LOOP
FETCH Crs_c1 INTO Rec_Crs_c1;
EXIT WHEN Crs_c1%NOTFOUND;
INSERT INTO conto_gdw (C_CONTO_LOCAL_GDW,
S_CONTO_LOCAL_GDW,
C_CONTO_GLOBAL_GDW,
T_CONTO,
D_INIZIO,
GLOBAL_LOCAL)
VALUES (Rec_Crs_c1.LOCAL_ACCOUNT_CODE,
NVL (Rec_Crs_c1.LOCAL_ACCOUNT_NAME, '.'),
Rec_Crs_c1.GLOBAL_ACCOUNT_CODE,
Rec_Crs_c1.t_conto,
(v_anno_in * 100) + v_mese_in,
Rec_Crs_c1.GLOBAL_LOCAL);
END LOOP;
CLOSE Crs_c1;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error Message' || SQLERRM);
DBMS_OUTPUT.put_line( 'GLOBAL_ACCOUNT_CODE :'
|| Rec_Crs_c1.LOCAL_ACCOUNT_CODE
|| 'LOCAL_ACCOUNT_CODE '
|| Rec_Crs_c1.LOCAL_ACCOUNT_NAME
|| 'GLOBAL_ACCOUNT_CODE :'
|| Rec_Crs_c1.GLOBAL_ACCOUNT_CODE
|| 'GLOBAL_ACCOUNT_TYPE '
|| Rec_Crs_c1.t_conto
|| 'GLOBAL_LOCAL'
|| Rec_Crs_c1.GLOBAL_LOCAL);
END;

Resources