How do i use a cursor correctly - sql

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!!

Related

Inserting values into table of records type

I am creating a procedure and I want to store multiple values into table of records type and I am trying to insert values by cursor but I get this errors :
Error(14,7): PL/SQL: Statement ignored
Error(14,12): PLS-00306: wrong number or types of arguments in call to
'+'
Error(15,7): PL/SQL: Statement ignored
Error(15,12): PLS-00382: expression is of wrong type
The lines that are mentioned in errors are these two :
n := n+1;
ulaz(n) := bank_id_rec(tmp_row.bank_id);
create or replace PROCEDURE BULK_STATUS_JOB
IS
CURSOR tmp_cursor
IS
SELECT bank_id FROM mdm_tbank_customer;
n INTEGER :=0;
ulaz bank_id_tab;
izlaz bank_service_status_tab;
rec itf_return_rec;
BEGIN
OPEN tmp_cursor;
LOOP
FOR n in tmp_cursor LOOP
n := n+1;
ulaz(n) := bank_id_rec(tmp_row.bank_id);
END LOOP;
EXIT
WHEN tmp_cursor%notfound;
END LOOP;
rec := mdm_tbank_itf_sb.get_tbank_service_status_bulk(ulaz, izlaz);
FOR i IN izlaz.first..izlaz.last
LOOP
DBMS_OUTPUT.PUT_LINE(ulaz(i).bank_id);
DBMS_OUTPUT.PUT_LINE(izlaz(i).bank_id || ': '||izlaz(i).service_status);
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
IF tmp_cursor%ISOPEN THEN
CLOSE tmp_cursor;
END IF;
END BULK_STATUS_JOB;
You have an issue in
FOR n in tmp_cursor LOOP
n := n+1;
You have used the same name for loop identifier and local variable but when it is used in the scope of the loop, n represents tmp_cursor. so
n := n+1 represents tmp_cursor := tmp_cursor + 1; which is invalid.
So please change the identifier to some other name (let's say nn) and use it all over the loop as follows.
FOR nn in tmp_cursor LOOP -- nn represents loop identifier
n := n+1; -- n represents your local variable declared by you
Also, there are few other issues which seem unnecessary, see the comments in the following final code
create or replace PROCEDURE BULK_STATUS_JOB
IS
CURSOR tmp_cursor
IS
SELECT bank_id FROM mdm_tbank_customer;
n INTEGER :=0;
ulaz bank_id_tab;
izlaz bank_service_status_tab;
rec itf_return_rec;
BEGIN
--OPEN tmp_cursor; -- not needed
--LOOP -- not needed
FOR nn in tmp_cursor LOOP -- changed loop variable name to nn
n := n+1;
ulaz(n) := bank_id_rec(nn.bank_id); --- used nn.bank_id
END LOOP;
--EXIT -- not needed
--WHEN tmp_cursor%notfound; -- not needed
--END LOOP; -- not needed
rec := mdm_tbank_itf_sb.get_tbank_service_status_bulk(ulaz, izlaz);
FOR i IN izlaz.first..izlaz.last
LOOP
DBMS_OUTPUT.PUT_LINE(ulaz(i).bank_id);
DBMS_OUTPUT.PUT_LINE(izlaz(i).bank_id || ': '||izlaz(i).service_status);
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
IF tmp_cursor%ISOPEN THEN
CLOSE tmp_cursor;
END IF;
END BULK_STATUS_JOB;
Cheers!!

checking if the record is already in the database [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
What is the fastest way to check if the record is already in the database.
I don't have unique id.
It could be checked after values as name, surname, date of birth
Generally the fastest way to check for the existence of a row is to use the SELECT-INTO (implicit cursor) statement, as opposed to the explicit cursor. It is consistently faster when a row is found. When a row is not found, SELECT-INTO raises NO_DATA_FOUND and exception handling is relatively slow.
So when you write your code, think about the context, think about the data and patterns of usage. Do you expect the query to usually find a row? Then go with SELECT-INTO. Do you think that usually the fetch will fail? Then go with the explicit cursor.
Of course as noted by #Randy you do need to make sure you have the correct indexes in place to ensure an optimized lookup.
My blog post on this topic should help fill in details and give you some code against which to exercise these ideas. But I will also post here to make it easier.
CREATE OR REPLACE PACKAGE tmr
IS
PROCEDURE start_timer;
PROCEDURE show_elapsed (str IN VARCHAR2);
END tmr;
/
CREATE OR REPLACE PACKAGE BODY tmr
IS
last_timing NUMBER := NULL;
PROCEDURE start_timer
IS
BEGIN
last_timing := DBMS_UTILITY.get_time;
END;
PROCEDURE show_elapsed (str IN VARCHAR2)
IS
BEGIN
DBMS_OUTPUT.put_line (
str
|| ': '
|| MOD (DBMS_UTILITY.get_time - last_timing + POWER (2, 32),
POWER (2, 32)));
start_timer;
END;
END tmr;
/
CREATE TABLE not_much_stuff (n NUMBER)
;
INSERT INTO not_much_stuff
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL < 11
;
-- Demonstration of Exception Behavior with SELECT-INTO
DECLARE
my_n not_much_stuff.n%TYPE;
BEGIN
DBMS_OUTPUT.put_line ('No rows found:');
BEGIN
SELECT n
INTO my_n
FROM not_much_stuff
WHERE n = -1;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);
END;
DBMS_OUTPUT.put_line ('Too many rows found:');
BEGIN
SELECT n
INTO my_n
FROM not_much_stuff
WHERE n BETWEEN 1 AND 10;
EXCEPTION
WHEN TOO_MANY_ROWS
THEN
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);
END;
END;
/
DECLARE
my_n not_much_stuff.n%TYPE;
BEGIN
tmr.start_timer;
FOR indx IN 1 .. 10000
LOOP
BEGIN
SELECT n
INTO my_n
FROM not_much_stuff
WHERE n = -1;
my_n := 100;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
my_n := 100;
END;
END LOOP;
DBMS_OUTPUT.put_line (my_n);
tmr.show_elapsed ('Implicit Failure');
END;
/
DECLARE
my_n not_much_stuff.n%TYPE;
BEGIN
tmr.start_timer;
FOR indx IN 1 .. 10000
LOOP
BEGIN
SELECT n
INTO my_n
FROM not_much_stuff
WHERE n = 1;
my_n := 100;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
my_n := 100;
END;
END LOOP;
DBMS_OUTPUT.put_line (my_n);
tmr.show_elapsed ('Implicit Success');
END;
/
DECLARE
my_n not_much_stuff.n%TYPE;
CURSOR stuff_cur
IS
SELECT n
FROM not_much_stuff
WHERE n = -1;
BEGIN
tmr.start_timer;
FOR indx IN 1 .. 10000
LOOP
OPEN stuff_cur;
FETCH stuff_cur INTO my_n;
IF stuff_cur%NOTFOUND
THEN
my_n := 100;
END IF;
CLOSE stuff_cur;
END LOOP;
DBMS_OUTPUT.put_line (my_n);
tmr.show_elapsed ('Explicit Failure');
END;
/
DECLARE
my_n not_much_stuff.n%TYPE;
CURSOR stuff_cur
IS
SELECT n
FROM not_much_stuff
WHERE n = 1;
BEGIN
tmr.start_timer;
FOR indx IN 1 .. 10000
LOOP
OPEN stuff_cur;
FETCH stuff_cur INTO my_n;
IF stuff_cur%FOUND
THEN
my_n := 100;
END IF;
CLOSE stuff_cur;
END LOOP;
DBMS_OUTPUT.put_line (my_n);
tmr.show_elapsed ('Explicit Success');
END;
/
-- 1. Implicit cursor inside a nested block
CREATE OR REPLACE PROCEDURE do_stuff_with_employee (
employee_id_in IN hr.employees.employee_id%TYPE)
IS
l_name hr.employees.last_name%TYPE;
BEGIN
BEGIN
SELECT last_name
INTO l_name
FROM hr.employees e
WHERE e.employee_id = do_stuff_with_employee.employee_id_in;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
/* log the error if this really is an error or let it go... */
l_name := NULL;
END;
IF l_name IS NOT NULL
THEN
/* continue with application logic */
NULL;
END IF;
END;
/
-- 2. Implicit cursor inside a nested subprogram
CREATE OR REPLACE PROCEDURE do_stuff_with_employee (
employee_id_in IN hr.employees.employee_id%TYPE)
IS
l_name hr.employees.last_name%TYPE;
FUNCTION emp_name (employee_id_in IN hr.employees.employee_id%TYPE)
RETURN hr.employees.last_name%TYPE
IS
l_name hr.employees.last_name%TYPE;
BEGIN
SELECT last_name
INTO l_name
FROM hr.employees
WHERE employee_id = employee_id_in;
RETURN l_name;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
/* log the error if this really is an error or let it go... */
RETURN NULL;
END;
BEGIN
l_name := emp_name (employee_id_in);
IF l_name IS NOT NULL
THEN
/* continue with application logic */
NULL;
END IF;
END;
/
-- 3. Explicit cursor unconcerned with too many rows
CREATE OR REPLACE PROCEDURE do_stuff_with_employee (
employee_id_in IN hr.employees.employee_id%TYPE)
IS
l_name hr.employees.last_name%TYPE;
CURSOR name_cur
IS
SELECT last_name
FROM hr.employees e
WHERE e.employee_id = do_stuff_with_employee.employee_id_in;
BEGIN
OPEN name_cur;
FETCH name_cur INTO l_name;
CLOSE name_cur;
IF l_name IS NOT NULL
THEN
/* continue with application logic */
NULL;
END IF;
END;
/
-- 4. Explicit cursor that checks for too many rows
CREATE OR REPLACE PROCEDURE do_stuff_with_employee (
employee_id_in IN hr.employees.employee_id%TYPE)
IS
l_name hr.employees.last_name%TYPE;
l_name2 hr.employees.last_name%TYPE;
CURSOR name_cur
IS
SELECT last_name
FROM hr.employees e
WHERE e.employee_id = do_stuff_with_employee.employee_id_in;
BEGIN
OPEN name_cur;
FETCH name_cur INTO l_name;
FETCH name_cur INTO l_name2;
IF name_cur%FOUND
THEN
CLOSE name_cur;
RAISE TOO_MANY_ROWS;
ELSE
CLOSE name_cur;
END IF;
IF l_name IS NOT NULL
THEN
/* continue with application logic */
NULL;
END IF;
END;
/
you can do these things:
query for it. Add an index to help that query go faster
If eventually inserting:
Add a unique index - and insert the values - errors mean it is already there
Merge into it.
And I must say what is very often used for checking if the record is already in the database, but is
highly NOT recommended! That is counting of all rows just to check:
DECLARE
count_var NUMBER;
BEGIN
SELECT COUNT (*)
INTO count_var
FROM not_much_stuff
WHERE n = -1;
IF count_var = 0
THEN
DBMS_OUTPUT.put_line ('No rows found!');
ELSIF count_var > 1
THEN
DBMS_OUTPUT.put_line ('Too many rows found!');
ELSE
DBMS_OUTPUT.put_line ('One row exists!');
END IF;
END;
Please never use this! This is example of programming laziness!

Output multiple rows of data from cursor using if statement PL/SQL

I am trying to output multiple rows of data from a table called 'student' using pl/sql. I am able to output the first row of data but that is all. I am familiar with loop procedures in other programming languages but am new to pl/sql here is what I have:
DECLARE
variable_Score Number;
variable_LetterGrade Char(1);
name char(10);
cursor studentPtr is SELECT name, grade from STUDENT;
BEGIN
open studentPtr;
LOOP
fetch studentPtr into name, variable_Score;
IF variable_Score >= 90 THEN
variable_LetterGrade := 'A';
ELSIF variable_Score >= 80 THEN
variable_LetterGrade := 'B';
ELSIF variable_Score >= 70 THEN
variable_LetterGrade := 'C';
ELSIF variable_Score >= 60 THEN
variable_LetterGrade := 'D';
ELSE
variable_LetterGrade := 'F';
END IF;
DBMS_OUTPUT.PUT_LINE('Hello '||name||', you receive a '||variable_LetterGrade||' for the class');
EXIT;
END LOOP;
END;
/
The output is :
Hello Joe , you receive a B for the class
This is just the first row from the student table though, there is three more rows of data I would like to output.
Problem is with the EXIT statement. A bare exit statement skips the loop and comes out displaying the row from first fetch.
So, remove the EXIT; before END LOOP and include an EXIT statement like this after your fetch.
..
fetch studentPtr into name, variable_Score;
EXIT WHEN studentPtr%NOTFOUND;
..
..
%NOTFOUND is a cursor attribute and becomes true when all rows are fetched.
what the statement does is that it skips the loop only when no rows could be fetched from the cursor.

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

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.

Pass multiple values to a single parameter in PL/SQL and insert them into a table

I want to pass a multiple values like '1,2,3,4,5,6,7,8,9,10,11,12' on as a single parameter and insert them into a table.
I have a scenario like saving a bill for particular customer, the customer might have purchased multiple items.
You can pass the values comma separated. This PL/SQL procedure breaks up a comma-separated string (passed as P_VALUE) and inserts each token into a table as a separate row, along with some other values.
PROCEDURE XXXXGL_INST_PARAMS (P_VALUE VARCHAR2) IS
i number;
n number;
r number;
pos number;
L_NAT_ACCT varchar2(10);
L_UID number;
BEGIN
L_UID := fnd_global.user_id;
n := length(P_VALUE);
i := 1;
r := 1;
WHILE (i < n) LOOP
pos := (INSTR(P_VALUE,',',1,r)-i);
IF pos < 0 THEN
pos := n;
END IF;
SELECT substr(P_VALUE,i,pos) INTO L_NAT_ACCT FROM dual;
INSERT INTO xxXXX.xxXXXgl_natural_accts_t
(NATURAL_ACCT, CREATION_DATE, CREATED_BY, LAST_UPDATE_LOGIN,
LAST_UPDATE_DATE, LAST_UPDATED_BY)
VALUES(L_NAT_ACCT,SYSDATE,L_UID,L_UID,SYSDATE,L_UID);
i := i + length(L_NAT_ACCT)+1;
r := r+1;
END LOOP;
END;
Note: I found this here.