PL SQL Cursor For Loop: add up values in same variable and output the summed total - sql

I'm trying to calculate the GPA within a function that accepts a studentID as an input. My issue is that the variable lv_gpa_calc isn't adding to itself when the loop works through the cursor set. I added the DBMS_OUTPUT.PUT_LINE to make sure that it's working through the cursor set correctly and that prints to the screen the correct individual row values of what lv_gpa_calc should be, but when it gets returned in the function in a SQL block, it isn't adding all of those values together. Can you not set a variable to itself within a CURSOR FOR LOOP?
Update: initializing the lv_gpa_calc fixed the problem where the variable value wasn't adding to itself.
CREATE OR REPLACE
FUNCTION CALCULATE_GPA
(p_studentID IN number)
RETURN NUMBER
IS
CURSOR cur_gpa IS
SELECT grade, grade_value, credit_hours
FROM grade
JOIN enrollment USING (Grade)
JOIN section USING (term_code, subject_code, course_number, section)
JOIN course USING (subject_code, course_number)
WHERE student_ID = p_studentID;
lv_gpa_calc NUMBER(4,2):=0;
BEGIN
FOR rec_gpa IN cur_gpa LOOP
lv_gpa_calc:= lv_gpa_calc + ((rec_gpa.grade_value * rec_gpa.credit_hours)/rec_gpa.credit_hours);
DBMS_OUTPUT.PUT_LINE(lv_gpa_calc);
END LOOP;
RETURN lv_gpa_calc;
END CALCULATE_GPA;

The problem in your code is that variable lv_gpa_calc is not initialized. Adding whatever to NULL will result as NULL.
Simplified working test case:
--DROP TABLE my_numbers;
CREATE TABLE my_numbers (
id NUMBER
);
/
BEGIN
FOR l_i IN 1..10 LOOP
INSERT INTO my_numbers VALUES (DBMS_RANDOM.RANDOM);
END LOOP;
COMMIT;
END;
/
SELECT * FROM my_numbers;
/
DECLARE
CURSOR cur IS
SELECT id
FROM my_numbers;
l_sum NUMBER(10) := 0;
BEGIN
FOR rec IN cur LOOP
l_sum := l_sum + rec.id;
DBMS_OUTPUT.PUT_LINE('l_sum = ' || l_sum);
END LOOP;
DBMS_OUTPUT.PUT_LINE('Sum = ' || l_sum);
END;
/
Important line is:
l_sum NUMBER(10) := 0;
Without initialization :=0 it won't work.

Related

How do i use a cursor correctly

With the code below I am trying to search for certain words (SEED WORDS) inside reviews. Then depending on the number of Seed words inside each review I come up with a rating number. There are multiple reviews per hotel ID. I am trying to get this entered into a Ratings table that has the following columns (HOTEL_ID, CATEGORY_ID,RATING). Each Seed Word has a category associated with it 1,2,3 or 4. The error I have received so far says 'LV_RATE_NUM' must be declared.
DECLARE
lv_hotel NUMBER(4,0);
lv_rate_num NUMBER(4);
lv_rate NUMBER(1,1);
lv_pol NUMBER(1,1);
i number(4,0);
CURSOR cur_rate IS
SELECT a.IDHOTEL, INSTR(a.review,b.seed_words), b.polarity
INTO lv_hotel, lv_rate_num,lv_pol
FROM review a, SEEDWORDS b;
BEGIN
i :=1;
lv_hotel := i;
FOR rec_hotel IN cur_rate LOOP
IF rec_hotel.lv_rate_num > 0
THEN lv_rate := lv_rate_num;
ELSIF rec_hotel.lv_rate_num = 0
THEN lv_rate_num := 8.6;
i := i+1;
END IF;
END LOOP;
INSERT INTO RATINGS
VALUES (lv_hotel, 'CATID',lv_rate);
END;
The INTO clause is only used in a singleton SELECT - that is, a SELECT which only returns one row. Your code can be simplified quite a bit:
DECLARE
i NUMBER := 1;
lv_hotel NUMBER := 1;
lv_rate NUMBER;
BEGIN
FOR rec_hotel IN (SELECT INSTR(a.review, b.seed_words) AS LV_RATE_NUM
FROM review a
CROSS JOIN SEEDWORDS b)
LOOP
IF rec_hotel.LV_RATE_NUM > 0 THEN
lv_rate := rec_hotel.LV_RATE_NUM;
ELSIF rec_hotel.LV_RATE_NUM = 0 THEN
lv_rate := 8.6;
i := i+1;
END IF;
END LOOP;
INSERT INTO RATINGS(HOTEL_ID, CATEGORY_ID,RATING)
VALUES (lv_hotel, 'CATID', lv_rate);
END;
I strongly recommend that you avoid specifying precision and scale on numbers. They are very seldom needed and are a source of potential problems. Note that I'm pretty sure that this still won't do what you intend, but you can use it as a way to get started.
You are getting the error because you have used into in cursor query. You can give alias to the cursor columns and use that column names in the loop.
DECLARE
lv_hotel NUMBER(4,0);
lv_rate_num NUMBER(4);
lv_rate NUMBER(1,1);
lv_pol NUMBER(1,1);
i number(4,1);
CURSOR cur_rate IS
SELECT a.IDHOTEL, INSTR(a.review,b.seed_words) as rate_num, b.polarity as pol
--INTO lv_hotel, lv_rate_num,lv_pol
FROM review a, SEEDWORDS b;
BEGIN
i :=1;
lv_hotel := i;
FOR rec_hotel IN cur_rate LOOP
IF rec_hotel.rate_num > 0 -- used cursor column alias name
THEN lv_rate := lv_rate_num; -- lv_rate_num is not initialized and you are using it here. You must Consider changing the logic
ELSIF rec_hotel.rate_num = 0
THEN lv_rate_num := 8.6;
i := i+1;
END IF;
END LOOP;
INSERT INTO RATINGS
VALUES (lv_hotel, 'CATID',lv_rate);
END;
Your Insert statement is outside the loop so it will insert only one record in the table.
Cheers!!

Oracle SQL dynamic nr of FOR LOOP iterations

I would like to run the FOR LOOP in Oracle based on a parameter that is dynamically populated. Please see, how the code could look like:
declare
idx number := (select max(field_name) from table_name);
begin
for i in 1..idx loop
dbms_output.put_line (i);
end loop;
end;
So basically if the select max(field_name) from table_name returns for example 10, the loop should be run 10 times (for i in 1..10 loop).
Assuming that field_name is an integer, you only need to fetch the max value in the right way:
declare
idx number;
begin
select max(field_name)
into idx
from table_name;
if idx is not null then -- to handle the case where the table is empty
for i in 1..idx loop
dbms_output.put_line (i);
end loop;
end if;
end;
or, without the IF:
declare
idx number;
begin
select nvl(max(field_name), 0)
into idx
from table_name;
for i in 1..idx loop
dbms_output.put_line (i);
end loop;
end;

PL/SQL Procedure Use String Select Statement in for loop

I am building a procedure, where I`m first creating a select statement and store it in an VARCAHR variable.
I now want to execute that query and store the whole result set in an variable to loop through it or use directly in a for loop.
I only find examples where the Select is hard written in the for loop definition.
How do i exchange the Select statement with my variable that holds my select statement?
for r IN (SELECT ... FROM ...)
loop
--do sth;
end loop;
how i want to use it :
statement := 'SELECT .... FROM ...';
for r IN (statement) -- HOW TO DO THIS
loop
--do sth;
end loop;
For a dynamic ref cursor, you need to define everything explicitly:
declare
sqlstring long := 'select 123 as id, ''demo'' as somevalue from dual where dummy = :b1';
resultset sys_refcursor;
type demo_rectype is record
( id integer
, somevalue varchar2(30) );
demorec demo_rectype;
begin
open resultset for sqlstring using 'X';
loop
fetch resultset into demorec;
exit when resultset%notfound;
dbms_output.put_line('id=' || demorec.id || ' somevalue=' || demorec.somevalue);
end loop;
close resultset;
end;
You can parse the cursor and figure out the column names and datatypes with DBMS_SQL. Example here: www.williamrobertson.net/documents/refcursor-to-csv.shtml

Function to return COUNT value without COUNT

The requirements are as follows:
Create a PL/SQL function called findtotalcarmodels to return the total number of cars belonging to a particular model. The function should have a single IN parameter as model_name. You should then use an explicit cursor to count the number of cars belonging to that car model and return the final count. You must NOT use any implicit cursors, table joins, subqueries, set operators, group functions or SQL functions (such as COUNT) to create this function.
The code I have come up with so far is:
CREATE OR REPLACE Function findtotalcarmodels
(model_name IN varchar2)
RETURN NUMBER
IS
CURSOR car_count_cur IS
SELECT model_name FROM i_car;
Rec_car_details car_count_cur%ROWTYPE;
BEGIN
OPEN car_count_cur;
LOOP
FETCH car_count_cur INTO Rec_car_details;
EXIT WHEN car_count_cur%NOTFOUND;
END LOOP;
CLOSE car_count_cur;
RETURN Rec_car_details;
END;
I am getting the following errors:
Errors for FUNCTION FINDTOTALCARMODELS:
LINE/COL ERROR
15/1 PL/SQL: Statement ignored
15/8 PLS-00382: expression is of wrong type
What am I doing wrong here?
You are trying to return the cursor from the inline function, and the function is looking to return an integer.
You need an (integer) counter to increment each time you iterate through the cursor... and then you can return that. I didn't test this, but this should work:
CREATE OR REPLACE Function findtotalcarmodels
(model_name_in IN varchar2)
RETURN NUMBER
IS
DECLARE counter INTEGER := 0;
CURSOR car_count_cur IS
SELECT model_name FROM i_car WHERE model_name = model_name_in;
Rec_car_details car_count_cur%ROWTYPE;
BEGIN
OPEN car_count_cur;
LOOP
FETCH car_count_cur INTO Rec_car_details;
EXIT WHEN car_count_cur%NOTFOUND;
counter := counter + 1;
END LOOP;
CLOSE car_count_cur;
RETURN counter;
END;
CREATE OR REPLACE FUNCTION findtotalcarmodels (vc_model_name IN VARCHAR2)
RETURN NUMBER
AS
CURSOR c1
IS
SELECT *
FROM i_car
WHERE UPPER (model_name) = UPPER (vc_model_name);
cnt NUMBER;
BEGIN
cnt := 0;
FOR i IN c1 LOOP
cnt := cnt + 1;
END LOOP;
RETURN cnt;
END;
You can check in dual table such as,
SELECT findtotalcarmodels('SUV1') FROM DUAL; -- here suv1 is your modelname

Oracle: IN function within an IF block

I have a cursor c2, with one column 'note', containing numbers.
I want to check, whether the column contains 5 and change my local variable if so.
this:
CREATE OR REPLACE PROCEDURE proc
IS
result varchar(50);
cursor c2 is
SELECT note
FROM student;
BEGIN
IF c2.note IN(5) THEN
result := 'contains 5';
DBMS_OUTPUT.PUT_LINE(result);
END;
/
doesnt work.
please help!
In your code, you're declaring a cursor but you are never opening it and never fetching data from it. You'd need, presumably, some sort of loop to iterate through the rows that the cursor returned. You'll either need to explicitly or implicitly declare a record into which each particular row is fetched. One option to do that is something like
CREATE OR REPLACE PROCEDURE proc
IS
result varchar(50);
cursor c2 is
SELECT note
FROM student;
BEGIN
FOR rec IN c2
LOOP
IF rec.note IN(5) THEN
result := 'contains 5';
DBMS_OUTPUT.PUT_LINE(result);
END IF;
END LOOP;
END;
/
Note that you also have to have an END IF that corresponds to your IF statement. Naming a cursor c2 is also generally a bad idea-- your variables really ought to be named meaningfully.
You have too loop over the records/rows returned in the cursor; you can't refer to the cursor itself like that:
CREATE OR REPLACE PROCEDURE proc
IS
result varchar(50);
cursor c2 is
SELECT note
FROM student;
BEGIN
FOR r2 IN c2 LOOP
IF r2.note = 5 THEN -- IN would also work but doesn't add anything
result := 'contains 5';
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE(result);
END;
/
But if you're just testing for the existence of any record with value 5 then you don't need a cursor:
CREATE OR REPLACE PROCEDURE proc
IS
result varchar(50);
BEGIN
SELECT max('contains 5')
INTO result
FROM student
WHERE note = 5;
DBMS_OUTPUT.PUT_LINE(result);
END;
/
If there are any rows with five you'll get the 'contains 5' string; if not you'll get null. The max() stops an exception being thrown if there are either zero or more than one matching records in the table.
You are missing an END IF and need to LOOP over the cursor. The procedure name is included in the final END.
But using IN should work. Like this:
CREATE OR REPLACE PROCEDURE proc
IS
result varchar(50);
cursor c2 is
SELECT note
FROM student;
BEGIN
FOR rec in c2
LOOP
IF rec.note IN (5) THEN
result := 'contains 5';
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE(result);
END proc;
/