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
..........
Related
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;
/
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.
I need to break out of the loop or not perform a loop when ITEM_ID is not found:
BEGIN
FOR item IN(SELECT ITEM.ITEM_ID,
ITEM.ITEM_DESC,
INVENTORY.INV_PRICE
FROM ITEM
INNER JOIN INVENTORY
ON ITEM.ITEM_ID = INVENTORY.ITEM_ID
WHERE ITEM.ITEM_ID = '1'
ORDER BY ITEM.ITEM_ID,
INVENTORY.INV_PRICE)
LOOP
DBMS_OUTPUT.PUT_LINE(
item.ITEM_ID||' '||item.ITEM_DESC||' ' ||item.INV_PRICE);
END LOOP;
END;
Also, I need to print out something like DBMS_OUTPUT.PUT_LINE('Item not found!');
There is no need to break out of the loop when an item is not found.
FOR record IN ( select-query ) LOOP statement(s) END LOOP is a cursor FOR LOOP in Oracle terminology, here is documentation: http://docs.oracle.com/cd/E18283_01/appdev.112/e17126/cursor_for_loop_statement.htm
The cursor for loop first executes the query, then for each record returned by the query, it executes the statements between LOOP...END LOOP.
When the query renturn no rows, then the loop code is not executed at all.
If we need to detect whether the query in the cursor for loop returns rows some rows or not, then the easiest way is to declare a boolean variable and assign a value to in wihin the LOOP..END LOOP block:
DECLARE
rows_found BOOLEAN := false;
BEGIN
FOR record IN ( select-query )
LOOP
rows_found := true;
... do something else ....
END LOOP:
IF NOT rows_found THEN
DBMS_OUTPUT.PUT_LINE('Item not found!');
END IF;
END;
Q: So then could I do it where it checks both tables? Or if the query doesn't return anything to display a message?
A: Perhaps FULL JOIN will do the trick. Try and let us know.
BEGIN
FOR item IN(SELECT ITEM.ITEM_ID,
ITEM.ITEM_DESC,
INVENTORY.INV_PRICE
FROM ITEM
FULL JOIN INVENTORY
ON ITEM.ITEM_ID = INVENTORY.ITEM_ID
WHERE ITEM.ITEM_ID = '1')
LOOP
IF ITEM.ITEM_ID IS NULL OR INVENTORY.ITEM_ID IS NULL THEN
EXIT;
ELSE
DBMS_OUTPUT.PUT_LINE(item.ITEM_ID||' '||item.ITEM_DESC||' ' ||item.INV_PRICE);
END IF;
END LOOP;
-- in PLSQL if query doesn’t return anything exception is raised
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('NO_DATA_FOUND');
END;
I need to optimize a PL/SQL function that is currently like that:
CREATE OR REPLACE FUNCTION tkt_get_underlying(n_input number)
RETURN t_table_of_number
IS
ret t_table_of_number;
CURSOR c IS SELECT n_number FROM t_table WHERE n_prop_1=n_input OR n_prop_2=n_input OR n_prop_3=n_input;
BEGIN
ret := t_table_of_number();
OPEN c;
FETCH c BULK COLLECT INTO ret;
CLOSE c;
RETURN ret;
END;
I want to be able to give an array as argument, however, I don't know how to build my cursor to take to array. I think I could use the IN statement, but could you help me settle this down please ?
EDIT:
According to solution provided by Justin Cave, it would become:
CREATE OR REPLACE FUNCTION tkt_get_underlying(n_inputs t_table_of_number)
RETURN t_table_of_number
IS
ret t_table_of_number;
CURSOR c IS SELECT n_number FROM t_table WHERE n_prop_1 IN (SELECT column_value FROM TABLE(n_inputs))
OR n_prop_2 IN (SELECT column_value FROM TABLE(n_inputs))
OR n_prop_3 IN (SELECT column_value FROM TABLE(n_inputs));
BEGIN
ret := t_table_of_number();
OPEN c;
FETCH c BULK COLLECT INTO ret;
CLOSE c;
RETURN ret;
END;
However, the multiple SELECT column_value FROM TABLE(n_inputs) slow the entire function. How can I improve that ?
If you want to pass in a collection of n_input values and return the same t_table_of_number collection (i.e. you don't need to know which element of the output array was associated with which element of the input array)
CREATE OR REPLACE FUNCTION tkt_get_underlying(p_inputs t_table_of_number)
RETURN t_table_of_number
IS
ret t_table_of_number;
CURSOR c
IS SELECT n_number
FROM t_table
WHERE n_prop IN (SELECT column_value
FROM TABLE( p_inputs ) );
BEGIN
OPEN c;
FETCH c BULK COLLECT INTO ret;
CLOSE c;
RETURN ret;
END;
This assumes that the number of elements that is going to potentially be inserted into the ret collection is still reasonable to hold in PGA memory simultaneously. Depending on the situation, you may want to transform this into a pipelined table function in order to limit the amount of PGA memory required.
Oracle is getting the cardinality wrong using the nested table, since it will have no idea how many rows are actually there. Try making your function look like:
CREATE OR REPLACE FUNCTION tkt_get_underlying(n_inputs t_table_of_number)
RETURN t_table_of_number
IS
ret t_table_of_number;
CURSOR c IS SELECT n_number FROM t_table WHERE n_prop_1 IN (SELECT /*+ cardinality(ni 1) */ column_value FROM TABLE(n_inputs) ni)
OR n_prop_2 IN (SELECT /*+ cardinality(ni 1) */ column_value FROM TABLE(n_inputs) ni)
OR n_prop_3 IN (SELECT /*+ cardinality(ni 1) */ column_value FROM TABLE(n_inputs) ni);
BEGIN
ret := t_table_of_number();
OPEN c;
FETCH c BULK COLLECT INTO ret;
CLOSE c;
RETURN ret;
END;
Note, if you know how many rows you expect in the nested table, make your cardinality hint accurate. Also, if you put too many rows in the nested table, Oracle could perform sub-optimally because you are making it think there are less rows in the nested table than what it really has.
Thank you for all your help, I finally find THE optimization that fit my needs. Now the query is like:
CREATE OR REPLACE FUNCTION tkt_get_underlying(n_inputs t_table_of_number)
RETURN t_table_of_number
IS
ret t_table_of_number;
CURSOR c IS SELECT t.n_number FROM t_table t, (SELECT column_value /*+cardinality(t_inputs 100) */ c FROM TABLE(n_inputs)) t_inputs
WHERE t_inputs.c = t.n_prop_1
OR t_inputs.c = t.n_prop_2
OR t_inputs.c = t.n_prop_3;
BEGIN
ret := t_table_of_number();
OPEN c;
FETCH c BULK COLLECT INTO ret;
CLOSE c;
RETURN ret;
END;
It does a JOIN that is better than a IN
I am trying to create a trigger in Oracle. I know sql but i have never created trigger before. I have this code:
create or replace trigger "PASSENGER_BOOKING_T1"
AFTER
insert on "PASSENGER_BOOKING"
for each row
begin
IF (:NEW.CLASS_TYPE == 'ECO')
SELECT F.AVL_SEATS_ECOCLASS,F.FLIGHT_ID INTO SEAT, FLIGHT_INFO
FROM BOOKING B, JOURNEY_FLIGHT J, FLIGHT F
WHERE B.JOURNEY_ID = J.JOURNEY_ID and F.FLIGHT_ID = J.FLIGHT_ID;
UPDATE FLIGHT
SET AVL_SEATS_ECOCLASS = (SEAT-1)
WHERE FLIGHT_ID = FLIGHT_INFO;
END IF;
end;
This trigger fires when there is an insert in Passenger_Booking table. And seating capacity is reduced by one (which is at different table).
Select query should be alright but there is something wrong in somewhere.
Could anyone suggest anything?
I changed the body part to this but still having issues:
UPDATE FLIGHT
SET AVL_SEATS_ECOCLASS =
(SELECT F.AVL_SEATS_ECOCLASS FROM BOOKING B, JOURNEY_FLIGHT J, FLIGHT F WHERE B.JOURNEY_ID = J.JOURNEY_ID and F.FLIGHT_ID = J.FLIGHT_ID;
);
An IF statement needs a THEN
In PL/SQL, you use an = to test for equality, not ==
You need to declare the variables that you are selecting into
When I do those three things, I get something like this
create or replace trigger PASSENGER_BOOKING_T1
AFTER insert on PASSENGER_BOOKING
for each row
declare
l_seat flight.seat%type;
l_flight_id flight.flight_id%type;
begin
IF (:NEW.CLASS_TYPE = 'ECO')
THEN
SELECT F.AVL_SEATS_ECOCLASS,F.FLIGHT_ID
INTO l_seat, l_flight_id
FROM BOOKING B,
JOURNEY_FLIGHT J,
FLIGHT F
WHERE B.JOURNEY_ID = J.JOURNEY_ID
and F.FLIGHT_ID = J.FLIGHT_ID;
UPDATE FLIGHT
SET AVL_SEATS_ECOCLASS = (l_seat-1)
WHERE FLIGHT_ID = l_flight_id;
END IF;
end;
Beyond those syntax errors, I would be shocked if the SELECT INTO statement was correct. A SELECT INTO must return exactly 1 row. Your query should almost certainly return multiple rows since there are no predicates that would restrict the query to a particular flight or a particular booking. Presumably, you want to join to one or more columns in the PASSENGER_BOOKING table.
Additionally, if this is something other than a homework assignment, make sure you understand that this sort of trigger does not work correctly in a multi-user environment.
just a wild guess
edit as Justin pointed out (thanks Justin) equality check
create or replace trigger "PASSENGER_BOOKING_T1"
AFTER
insert on "PASSENGER_BOOKING"
for each row
declare
v_flight_id FLIGHT.FLIGHT_ID%TYPE;
begin
IF (:NEW.CLASS_TYPE = 'ECO') THEN
SELECT F.ID into v_flight_id
FROM BOOKING B, JOURNEY_FLIGHT J, FLIGHT F
WHERE B.ID = :NEW.BOOKING_ID -- note that I've made this up
AND B.JOURNEY_ID = J.JOURNEY_ID AND F.FLIGHT_ID = J.FLIGHT_ID;
UPDATE FLIGHT
SET AVL_SEATS_ECOCLASS = (SEAT-1)
WHERE ID = v_flight_id;
END IF;
end;