Cursor with a variable as a parameter Oracle - database

I have a function with a cursor. Within this cursor I want to get another cursor which I pass a parameter . This parameter is a value of the primary cursor. The logic that I have is like this:
CURSOR cursor1 IS
SELECT * FROM SCHEMAP.TABLA1 ;
registro cursor1%ROWTYPE;
CURSOR cursor2 (parametro IN NUMBER) IS
SELECT * FROM SCHEMAP.TABLA2 WHERE CAMPO_1 = parametro;
registroVac cursor2%ROWTYPE;
..........
BEGIN
.......
OPEN cursor1;
FETCH cursor1 INTO registro;
WHILE cursor1%found
LOOP
dbms_output.put_line('VARIABLE1:' + registro.VARIABLE1 );
OPEN cursor2(registro.VARIABLE1);
FETCH cursor2 INTO registroVac;
WHILE cursor2%found
LOOP
SELECT HC3PKDMUTILITIES.GET_DIAGNOSTIC_CODE_VAC(registro.VARIABLE1,registroVac.VAC_DOS,registroVac.VAC_CVH)
into v_diagnostic_code
from DUAL;
dbms_output.put_line('v_diagnostic_code -->' || v_diagnostic_code);
FETCH cursor2 INTO registroVac;
END LOOP;
CLOSE cursor2;
FETCH cursor1 INTO registro;
END LOOP;
CLOSE cursor1;
When I run the process I have an error in cursor2 like this:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error CAMPO_1:102435313
CAMPO_1 is proven to be a numerical Database and registro.VARIABLE1 too. How to solve this problem?. Thanks.

Is this only a code-snipped and do you have more code in your application?
If not, then you can do it all in one which should be much faster and shorter:
CURSOR All_in_one IS
SELECT VARIABLE1,
HC3PKDMUTILITIES.GET_DIAGNOSTIC_CODE_VAC(registro.VARIABLE1,registroVac.VAC_DOS,registroVac.VAC_CVH) AS v_diagnostic_code
FROM SCHEMAP.TABLA2 t registroVac
RIGHT OUTER JOIN SCHEMAP.TABLA1 registro ON CAMPO_1 = VARIABLE1;

There are loads of things wrong with your code.
The thing that strikes me first is that you're opening cursor1, opening, fetching and closing cursor2, and then fetching from cursor1. That seems incorrect!
Secondly, why bother having two cursors and reinventing nested loop joins yourself? SQL is perfectly capable of handling joins, and the optimizer is pretty good at deciding which join to use (hash join, nested loops, etc).
Thirdly, if HC3PKDMUTILITIES.GET_DIAGNOSTIC_CODE_VAC is a function, why are you selecting it from dual (and therefore introducting context switching between the PL/SQL and SQL engines), rather than simply assigning the variable with the return value of the function?
I think you could rewrite the above code as simply:
declare
v_diagnostic_code varchar2(100); -- wasn't sure of the correct datatype; this is just a guess
begin
for rec in (select t1.variable1,
t2.vac_dos,
t2.vac_cvh
from schemap.tabla1 t1
inner join schemap.tabla2 t2 on (t2.campo_1 = t1.variable1))
loop
v_diagnostic_code := hc3pkdmutilities.get_diagnostic_code_vac(rec.variable1,rec.vac_dos,rec.vac_cvh);
dbms_output.put_line('variable1 --> '||rec.variable1||', v_diagnostic_code -->' || v_diagnostic_code);
end loop;
end;
/

Related

Cursor is returning more data that is available in table

I have query
select * from ot.city_vw;
The data coming is:
But when I write a pl/sql block to get the data then
declare
v_data OT.city%rowtype;
CURSOR cur1 is
select * from ot.city_vw;
begin
open cur1;
loop
fetch cur1 into v_data;
dbms_output.put_line(v_data.city_id);
dbms_output.put_line(v_data.city_name);
EXIT WHEN cur1%NOTFOUND;
END LOOP;
CLOSE cur1;
end;
/
the last table data is coming twice as below in picture:
what is the problem with my cursor? why is the last data coming twice?
Move EXIT up.
open cur1;
loop
fetch cur1 into v_data;
EXIT WHEN cur1%NOTFOUND; --> here
dbms_output.put_line(v_data.city_id);
dbms_output.put_line(v_data.city_name);
END LOOP;
CLOSE cur1;
Or, even better, user cursor FOR loop:
begin
for v_data in (select * from ot.city_vw)
loop
dbms_output.put_line(v_data.city_id);
dbms_output.put_line(v_data.city_name);
end loop;
end;
Doesn't it look simpler? Oracle does all the dirty job for you (declaring a cursor variable, opening the cursor, exiting the loop, closing the cursor). I suggest you use it whenever possible.

Nested table loops to format data, PL/SQL

I am trying to format data returned from a cursor to JSON by looping through the records and columns without having to explicitly call on each column name. From what I've researched this vary well may not be a simple task or at least as simple as I'm trying to make it. I'm wondering if anyone else has tried a similar approach and if they had any luck.
declare
type type_cur_tab is table of employees%rowtype
index by PLS_integer;
type type_col_tab is table of varchar2(1000)
index by binary_integer;
tbl_rec type_cur_tab;
tbl_col type_col_tab;
begin
select * BULK COLLECT INTO tbl_rec
from employees;
select column_name BULK COLLECT INTO tbl_col
from all_tab_columns
where UPPER(table_name) = 'EMPLOYEES';
for i IN 1..tbl_rec.COUNT Loop
for j IN 1..tbl_col.count Loop
dbms_output.put_line(tbl_rec(i).tbl_col(j));
end loop;
end loop;
end;
It throws an error saying 'tbl_col' must be declared. I'm sure this is bc it's looking for 'tbl_col' listed inside 'tbl_rec'. Any help is greatly appreciated.
NOTE: I'm aware of the built in JSON conversion but I haven't been able to get it to as fast as I'd like so I'm trying to loop through and add the appropriate formatting along the way.
It is impossible to specify field of tbl_rec(i) in this manner.
Try this:
declare
v_cur sys_refcursor;
col_cnt number;
desc_t dbms_sql.desc_tab;
c number;
vVarchar varchar2(32000);
vNumber number;
vDate date;
v_result clob:='';
rn number:=0;
begin
--Any sql query or pass v_cur as input parameter in function on procedure
open v_cur for
select * from dual;
--------
c:=dbms_sql.to_cursor_number(v_cur);
dbms_sql.describe_columns(c => c, col_cnt => col_cnt, desc_t => desc_t);
for i in 1 .. col_cnt
loop
case desc_t(i).col_type
when dbms_types.TYPECODE_DATE then
dbms_sql.define_column(c, i ,vDate);
when dbms_types.TYPECODE_NUMBER then
dbms_sql.define_column(c, i ,vNumber);
else
dbms_sql.define_column(c, i ,vVarchar,32000);
end case;
end loop;
v_result:='{"rows":{ "row": [';
while (dbms_sql.fetch_rows(c)>0)
loop
if rn > 1 then v_result:=v_result||','; end if;
v_result:=v_result||'{';
for i in 1 .. col_cnt
loop
if (i>1) then v_result:=v_result||','; end if;
case desc_t(i).col_type
--Date
when dbms_types.typecode_date then
dbms_sql.column_value(c,i,vDate);
v_result:=v_result||' "'||desc_t(i).col_name||'" :"'||to_char(vDate,'dd.mm.yyyy hh24:mi')||'"';
--Number
when dbms_types.typecode_number then
dbms_sql.column_value(c,i,vNumber);
v_result:=v_result||' "'||desc_t(i).col_name||'" :"'||to_char(vNumber)||'"';
--Varchar - default
else
dbms_sql.column_value(c,i,vVarchar);
v_result:=v_result||' "'||desc_t(i).col_name||'" :"'||vVarchar||'"';
end case;
end loop;
v_result:=v_result||'}';
end loop;
v_result:=v_result||']}}';
dbms_output.put_line (v_result);
end;
Also you can generate XML from ref cursor with DBMS_XMLGEN package and then translate xml into json with xslt transformation.

PL/SQL cursor for loop and record not working

I have the following problem. I'm trying to check a number (bsn), if it's in the database or not. If it's not in the database it should give me an error, however now I'm getting always an error even if the number exists in the database. It worked fine with only one number in the database, but with more... That's the problem. Oh and I'm working with APEX, so I use this as a process.
create or replace PROCEDURE CONTROLE_BSN IS
CURSOR c_klanten
IS
SELECT bsn
FROM klant;
v_bsn VARCHAR2(10) := V('P7_BSN');
e_geen_bsn EXCEPTION;
BEGIN
FOR r_record IN c_klanten
LOOP
IF r_record.bsn != v_bsn THEN
RAISE e_geen_bsn;
END IF;
END LOOP;
EXCEPTION
WHEN e_geen_bsn THEN
raise_application_error(-20001, 'This bsn-number does not exists.');
END CONTROLE_BSN;
Your logic is flowed. As soon as you have two different bsn in your table, your test will be true for at least one of them:
FOR r_record IN c_klanten
LOOP
IF r_record.bsn != v_bsn THEN --< when N different records,
-- this is true for at least N-1 of them
RAISE e_geen_bsn;
END IF;
END LOOP;
Maybe you should go for something a little bit simpler than that. Why not write your cursor like this instead:
CURSOR c_klanten
IS
SELECT count(*) n
FROM klant
WHERE nbc = v_bsn;
That way, you will easily get the number of matching bsn. Either 0, 1 or more. And then perform the appropriate action.
Perhaps the following would help:
create or replace PROCEDURE CONTROLE_BSN IS
CURSOR c_klanten(p_bsn) IS
SELECT count(*) as bsn_count
FROM klant
where bsn = p_bsn;
v_bsn VARCHAR2(10) := V('P7_BSN');
e_geen_bsn EXCEPTION;
BEGIN
FOR r_record IN c_klanten(v_bsn)
LOOP
IF r_record.bsn_count = 0 THEN
RAISE e_geen_bsn;
END IF;
END LOOP;
EXCEPTION
WHEN e_geen_bsn THEN
raise_application_error(-20001, 'This bsn-number does not exists.');
END CONTROLE_BSN;
Best of luck.

getting data from memory instead of table

I have a parameter table with 10 rows. Called parameter_table.
In my PL/SQL procedure, I do loop in 2 million records. And each time querying this parameter table too.
I want to load this parameter table in to the memory and decrease the I/O process.
What is the best way to do this?
FOR cur_opt
IN (SELECT customer_ID,
NVL (customer_type, 'C') cus_type
FROM invoice_codes
WHERE ms.invoice_type='RT')
LOOP
....
...
Select data From parameter_table Where cus_type = cur_opt.cus_type AND cr_date < sysdate ; -- Where clause is much complex than this..
....
...
END LOOP;
You can just join it to your main query:
select customer_id, data
from parameter_table t, invoice_codes c
where t.cus_type = nvl(c.customer_type, 'C')
and t.cr_date < sysdate
However, if you've got 2 million records in invoice_codes, then joining to the parameter table is the least of your concerns - looping through this will take some time (and is probably the real cause of your I/O problems).
I Think you may change the query ,joining to parameter_table, so there will be no need to hit the select statement inside the loop. (like what #Chris Saxon solution)
But as a way to use cashed data,
You could fill a dictionary like, array and then refer it when necessary
Something like this may help:
you have to call Fill_parameters_cash before starting the main process and call get_parameter to fetch the data, the input parameter to call get_parameter is the dictionary key
TYPE ga_parameter_t IS TABLE OF parameter_table%ROWTYPE INDEX BY BINARY_INTEGER;
ga_parameter ga_parameter_t;
procedure Fill_parameters_cash is
begin
ga_parameter.DELETE;
SELECT * BULK COLLECT
INTO ga_parameter
FROM parameter_table;
end Fill_parameters_cash;
FUNCTION get_parameter(cus_type invoice_codes.cus_type%TYPE,
is_fdound OUT BOOLEAN)
RETURN parameter_table%ROWTYPE IS
result_value parameter_table%ROWTYPE;
pos NUMBER;
BEGIN
result_value := NULL;
is_fdound := FALSE;
IF cus_type IS NULL THEN
RETURN NULL;
END IF;
pos := ga_parameter.FIRST;
WHILE pos IS NOT NULL
LOOP
EXIT WHEN ga_parameter(pos).cus_type = cus_type;
pos := ga_parameter.NEXT(pos);
END LOOP;
IF pos IS NOT NULL THEN
is_fdound := TRUE;
result_value := ga_parameter(pos);
END IF;
RETURN result_value;
END get_parameter;
I'd guess looping through a million records is already causing issues. Not quite sure how this parameter table lookup is really worsening it.
Anyways, if this is really the only approach you can take, then you could do an inner or outer join in the cursor declaration.
----
FOR cur_opt
IN (SELECT customer_ID,
NVL (customer_type, 'C') cus_type
FROM invoice_codes codes,
parameter_table par
WHERE ms.invoice_type='RT'
and codes.cus_type = par.cus_type -- (or an outer join) maybe?
) loop
..........

help, stored procedures and cursors

i have to write a stored procedure, where you give the month, and a credit card number, and it calulates 1% for each transaction made in the first 10 days of the month, 2% for transactions between 10 and 20, and 3% for transactions above 20.
And i must use cursors.
i wrote this code, but i get some errors when i try to run the precedure
create procedure cardP
/* month ex 1,3,5 etc*/
#minas int,
#cardNo bigint
as
/* creating the cursor*/
DECLARE sinallages CURSOR
FOR SELECT cc_number,day([DateTime]),charged_amount FROM transactions
where cc_number=#cardNo and month([DateTime])=#minas
/* declaring local variables*/
declare #poso int,#karta bigint,#mera int,#pos float,#pos2 float,#pos3 float,
#count int,#counter int
open sinallages
set #count=(select count(cc_number) from transactions where cc_number=#cardNo and month([DateTime])=#minas )
/* get 1st row*/
fetch sinallages into #karta,#mera,#poso
while (/*##sqlstatus != 2*/#counter<#count)
begin
if day(#mera)<=10
set #pos =#poso+ #poso * 0.01
else
if day(#mera)>10 and day(#mera)<=20
set #pos2 =#poso+ #poso * 0.02
else
if day(#mera) > 20
set #pos3 =#poso+ #poso * 0.03
fetch sinallages into #karta,#mera,#poso
set #counter=#counter+1
end
close sinallages
return
when i call the procedure i get
EXEC cardP #minas = 5, #cardNo =4929569752542450
Msg 16915, Level 16, State 1, Procedure cardP, Line 20
A cursor with the name 'sinallages' already exists.
Msg 16922, Level 16, State 1, Procedure cardP, Line 31
Cursor Fetch: Implicit conversion from data type datetime to int is not allowed.
thank you :) i now deallocate the cursor at the end of the stored procedure and removed the day(). Now i want to print the pos+pos2+pos3. I use print pos+pos2+pos3 but it doesnt print anything. why is that ??
................
set #counter=#counter+1
end
print #pos+#pos2+#pos3
close sinallages
return
DEALLOCATE sinallages;
it seems like hte variables po,pos2,pos3 are left null??
Others have suggested using DEALLOCATE. The problem with that is that, in some error situations, it won't be called. If you then attempt to use the same connection to call this stored proc, the cursor will still be allocated.
I'd prefer to instead declare the cursor as LOCAL, which means it's automatically deallocated when the stored proc is exited (whether normally or not).
Yes, you need to deallocate the cursor after closing it. Besides, if your query had an error before closing the cursor, it may have stayed open, so i recommend you execute the CLOSE and DEALLOCATE before executing your procedure again. For your second error, you are using the function DAY() over a variable of type INT, change if day(#mera)<=10 with if #mera<=10.
Update: Now that you fixed the problems you had, when you add each #pos variable, following your logic, one of them is always null, so you should add them like this:
Print isnull(#pos1,0)+isnull(#pos2,0)+isnull(#pos3,0)
After
close sinallages
You need to call
deallocate sinallages
Have a look at DEALLOCATE (Transact-SQL)
you have to dealocate the cursor after closing it:
DEALLOCATE sinallages
http://msdn.microsoft.com/en-us/library/ms188782.aspx

Resources