I'm using Oracle Forms 10g, on a Oracle Database version 7.
I have a data block who is refreshed (new execute_query) from two different ways:
1.A button how implement this:
PROCEDURE refresh
IS
ID NUMBER;
BEGIN
ID := :myblock.id;
GO_BLOCK ('myBlock');
EXECUTE_QUERY;
-- Keep the record selected after the refresh
POSITION (ID);
END;
PROCEDURE POSITION (ID IN NUMBER)
IS
L_record NUMBER (5) := NULL;
BEGIN
GO_BLOCK ('myBlock');
GO_ITEM ('myBlock.ID');
FIRST_RECORD;
LOOP
IF :myblock.id = myID THEN
L_record := GET_BLOCK_PROPERTY ('myBlock', CURRENT_RECORD);
END IF;
EXIT WHEN :SYSTEM.LAST_RECORD = 'TRUE' OR :myblock.ID = myID;
NEXT_RECORD;
END LOOP;
IF L_record IS NOT NULL THEN
GO_RECORD (L_record);
END IF;
END;
2.The other is one other button who do this:
PROCEDURE newRefresh
IS
ID NUMBER;
BEGIN
ID := :myblock.id;
refresh;
POSITION (ID);
END;
Ignoring the reason of this two buttons, my problem is when I call the second button, the first call of POSITION procedure takes too long (because we have a lot of records and POST_QUERY trigger), but the second call of the same procedure is very fast.
What is the reason of this behaviour? Is there a fastest way of positioning the focus on the same record selected before?
Why is it slow? Who knows ... All we can do is to blindly guess and still be VERY far from actual reason. Too few data to compute. I'd suggest you to run the form in debug mode (as Forms 10g allows it) and trace its execution to see what's going on.
As of faster positioning: consider this approach:
create a parameter, let's call it position
refresh button(s) would:
-- save current position
:parameter.position := :system.trigger_record;
go_block('myBlock');
execute_query;
-- go to previously saved position
go_record(:parameter.position);
(BTW, are you really using Oracle database version 7.x? Whoa!)
Related
i have this PL/SQL function
declare
v_sql varchar2(222);
s1 real;
s2 real;
p67_price real;
p67_type_project real;
begin
p67_price:=:p67_price;
p67_type_project:=:p67_type_projet;
select :limit_1_type_project into s1 from type_project where id_type_project=p67_type_project;
select :limit_2_type_project into s2 from type_project where id_type_project=p67_type_project;
if p67_price>=s1 then
v_sql:='select label_mode_pass, id_mode_pass from mode where id_mode_pass<4';
return v_sql;
end if;
if p67_price<s1 and p67_price>=s2 then
v_sql:='select label_mode_pass, id_mode_pass from mode where id_mode_pass=3 or id_mode_pass=2';
return v_sql;
end if;
if p67_price<s2 then
v_sql:='select label_mode_pass, id_mode_pass from mode where id_mode_pass<5';
return v_sql;
end if;
end;
that i tested and it works fine when both :p67_price and :p67_type_projet are given numeric values for example :
p67_price:=15000000;
p67_type_project:=2;
the problem is it won't work otherwise and the APEX compiler show this error message ORA-01403: no data found.
is it not possible to include region item's data in the list of values or is there another problem i am not seeing?
NO DATA FOUND means that one of SELECT statements didn't return anything because there's no row which satisfies WHERE condition.
If code you wrote works for values you mentioned (15000000 / 2) but not for other values, then you'll have to handle it somehow:
one option is to make sure to provide only valid values for price and type_project
another is to review where clauses; maybe you coded it wrong
the most obvious is to include the exception handling section; it begins with the exception keyword and ... well, handles the error. For example:
declare
s1 ...
s2 ...
begin
select ... into ... from ... where ...; --> this is SELECT which might raise the error
<do stuff if SELECT succeeds>
-- this is what you need
exception
when no_data_found then
-- handle it; this is just an example, you should know what to do
s1 := 0;
s2 := 0;
end;
Also, make sure that P67_ items you use in that code are stored into session state. One way to do that is to submit the page (by pressing a button). Or, if it is a list of values, you can use those P67_ items as parent items in cascading list of values, or submit their values (you'll find both properties in LoV items' property palette).
If you wonder "how come 15000000 / 2 combination works?", it might be because you did put those values into session state previously, and it stays so during your session. If you log off and log in again, their values will be lost and - I presume - your code won't work any more, at least not until those values enter session state again.
i found a solution to my problem, instead of
p67_price:=:p67_price;
i used
p67_price:=V('p67_price');
and of course i had to add this piece of code at the end
exception
when no_data_found then
v_sql:='select label_mode_pass, id_mode_pass from mode';
return v_sql;
I'm coding a complex PLSQL block (complex for me hahaha) to insert rows using information from the FOR LOOP CURSOR and add parameters to insert using a stored procedure. The current problem is there are around 200 rows to be inserted but when a simple row fail to insert all rows inserted broke and oracle execute a ROLLBACK command (I think so). So... How could I handle exceptions to insert succefully all rounds I can and when any rows fail show it in screen? Thanks
FOR i IN c_mig_saldos LOOP
IF i.tipo_comprobante = 'P' THEN -- Nota de debito (positivo)
v_cmp_p.prn_codigo := 'VIV';
v_cmp_p.tcm_codigo := 'NRA';
v_cmp_p.cmp_fecha_emision := TRUNC(SYSDATE);
v_cmp_p.cmp_fecha_contable := TRUNC(SYSDATE);
v_cmp_p.cmp_observacion := 'GENERACION AUTOMATICA DE SALDOS';
v_cmp_p.cli_codigo := i.cli_codigo;
v_tab_dco_p(1).cnc_codigo := 'VIA';
v_tab_dco_p(1).dco_precio_unitario := i.total_final;
v_tab_dco_p(1).dco_cantidad := 1;
v_tab_dco_p(1).dco_importe := i.total_final;
-- Insert a new row using stored procedure but when a itereted fail, no rows has inserted in table
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
COMMIT;
END IF;
END LOOP;
-- Insert a new row using stored procedure but when a itereted fail, no rows has inserted in table
begin
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
exception
when others then --this could be made more specific but you didn't say what type of error you were getting
-- Log to a table so you can export failed inserts later.
-- Make sure log table cols are large enough to store everything that can possibly be inserted here...
ErrorLogProc(the, cols, you, want, to, see, and, SQLERRM);
end;
In the ErrorLogProc() I'd recommend a couple things, here is a snippet of some things I do in my error logging proc that you may find helpful (it's just a few snippets, not intended to fully work, but you should get the idea)...
oname varchar2(100);
pname varchar2(100);
lnumb varchar2(100);
callr varchar2(100);
g1B CHAR(2) := chr(13)||chr(10);
PRAGMA AUTONOMOUS_TRANSACTION; --important!
begin
owa_util.who_called_me(oname, pname, lnumb, callr);
--TRIM AND FORMAT FOR ERRORLOG
lv_errLogText := 'Package: '||pname||' // Version: '||version_in||' // Line Number: '||lnumb||' // Error: ';
lv_string1 := mid(errStr_in,1,4000-Length(lv_errLogText));
lv_errLogText := lv_errLogText||lv_string1;
lv_errLogText := lv_errLogText||g1B||'Error Backtrace: '||replace(dbms_utility.format_error_backtrace,'ORA-', g1b||'ORA-');
insertIntoYourErrorLogTable(lv_errLogText, and, whatever, else, you, need);
commit;
To keep this simple, since there's not enough information to know the what and why of the question, this will kick out some text information about failures as desired.
SQL> set serveroutput on
Then here's an anonymous PL/SQL block:
BEGIN
FOR i IN c_mig_saldos
LOOP
-- not relevant
BEGIN
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
EXCEPTION
-- The goal of this is to ignore but output information about your failures
WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('whatever you want about v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p or SQLERRM/SQLCODE or the error stack - not enough info in the question');
END;
END LOOP;
END;
/
Note: I have removed the commit from the execution.
Then if you like what you see...
SQL> commit;
Ideally, if I knew more about why the insert failures were expected to occur and what I wanted to do about them, I would not insert them in the first place.
Agree with comment that more information is needed, but a couple of things to consider:
Does this need to be done as a loop - if you can write your query as a select statement, then you can do a simple insert without the need for PLSQL, which would be simpler and likely quicker (set based SQL vs row-by-row PLSQL)
You say a ROLLBACK is occuring - you have a commit inside your IF statement, so any records which make it into your IF statement and succesfully make it through your insert procedure will be committed i.e. they will not be rolled back; You should consider if some records you think are being rolled back are actually not making it into the IF statement at all
Can you provide example data, or an example of the error you are receiving?
I would like to know is there any way to receive information in PL/SQL how many rows have been updated and how many rows have been inserted while my PL/SQL script using MERGE DML statement.
Let's use Oracle example of merge described here: MERGE example
This functionality is used in my function but also I'd like to log information how many rows has beed updated and how many rows have been inserted.
There is a not a built-in way to get separate insert and update counts, no. SQL%ROWCOUNT would tell you the number of rows merged, as you probably already know, but there is no equivalent to get separate values for the inserts and updates.
This article by Adrian Billington shows a way to get the information by including a function call in the merge, which might add a little overhead.
There's a similar, and perhaps simpler, trick from MichaelS on the Oracle forums, which I can't take any credit for at all either, of course. I'm tempted to reproduce it here but I'm not sure if that's allowed, but essentially it's using sys_context to maintain a count, in much the same way that Adrian's solution did with a package variable. I'd use that one, as it's cleaner and I think it's easier to follow and maintain.
Still perilously close to a link-only answer but I don't want to plagiarise others' work either...
Workarounds pointed by #AlexPoole works, but for me it's strange why don't count updates, inserts and even possible deletes by more natural way with triggers.
Suppose we have simple test table:
create table test_table (id number, col number)
Define simple package for counters
create or replace package pkg_test_table_counter as
procedure reset_counter;
procedure count_insert;
procedure count_update;
procedure count_delete;
function get_insert_count return number;
function get_update_count return number;
function get_delete_count return number;
end;
... and package body:
create or replace package body pkg_test_table_counter as
vUpdateCount number := 0;
vInsertCount number := 0;
vDeleteCount number := 0;
procedure reset_counter is
begin
vUpdateCount := 0;
vInsertCount := 0;
vDeleteCount := 0;
end;
procedure count_insert is
begin
vInsertCount := vInsertCount + 1;
end;
procedure count_update is
begin
vUpdateCount := vUpdateCount + 1;
end;
procedure count_delete is
begin
vDeleteCount := vDeleteCount + 1;
end;
function get_insert_count return number is
begin
return vInsertCount;
end;
function get_update_count return number is
begin
return vUpdateCount;
end;
function get_delete_count return number is
begin
return vDeleteCount;
end;
end;
To count number of rows during execution of single DML statement we need to reset it in before statement trigger
create or replace trigger trg_test_table_counter_reset
before insert or update or delete on test_table
begin
pkg_test_table_counter.reset_counter;
end;
... and increment appropriate counter in trigger for each row:
create or replace trigger trg_test_table_counter_count
before insert or update or delete on test_table
for each row
begin
if inserting then
pkg_test_table_counter.count_insert;
end if;
if updating then
pkg_test_table_counter.count_update;
end if;
if deleting then
pkg_test_table_counter.count_delete;
end if;
end;
So, after executing MERGE statement without additional tricks inside DML query text it's always possible to get exact number of affected rows:
select
pkg_test_table_counter.get_insert_count insert_count,
(
pkg_test_table_counter.get_update_count
-
pkg_test_table_counter.get_delete_count
) update_count,
pkg_test_table_counter.get_delete_count delete_count
from dual
Note that delete operations also counted as updates for MERGE , but to keep package useful for another operations there are two separate counters.
SQLFiddle test
I have this cursor in a procedure in a package:
PROCEDURE CANCEL_INACTIVE(IN_DAYS_OLD NUMBER)
IS
CURSOR inactive IS
SELECT * FROM MY_TABLE
WHERE STATUS_CHANGED_DATE <= TRUNC(SYSDATE-IN_DAYS_OLD)
AND CANCEL_CD IS NULL;
rec inactive%ROWTYPE;
BEGIN
OPEN inactive;
LOOP
FETCH inactive INTO rec;
EXIT WHEN inactive%NOTFOUND;
-- do an update based on rec.id
END LOOP;
END;
END CANCEL_INACTIVE;
Every time I test or run the procedure, inactive always has zero rows. However, when I put the EXACT same query into a SQL window, I get the rows I'm looking for.
What the heck?
Probably you'are testing on noncommited data.
Or: you're not commiting your update based on rec.id.
Or: your update does nothing. (the where clause is not satisfied by any rows on target table)
I'm trying to randomly select a card from a table of cards with columns c_value and c_suit using a procedure. After selecting it, the procedure should update that entry's taken field to be 'Y'.
create or replace procedure j_prc_sel_card(p_value OUT number,
p_suit OUT number)
AS
CURSOR CUR_GET_RAND_CARD IS SELECT c_value,
c_suit
FROM (SELECT c_value,
c_suit,
taken
FROM jackson_card
ORDER BY dbms_random.value)
WHERE rownum = 1
FOR UPDATE OF taken;
BEGIN
OPEN CUR_GET_RAND_CARD;
FETCH CUR_GET_RAND_CARD into p_value, p_suit;
UPDATE jackson_card
SET taken = 'Y'
WHERE c_value = p_value
AND c_suit = p_suit;
CLOSE CUR_GET_RAND_CARD;
END;
Then I am trying to get the selected card and output what it is as a start. With this:
SET serveroutput on;
DECLARE v_value number;
v_suit number;
BEGIN
j_prc_sel_card(p_value => v_value,p_suit => v_suit);
DBMS_OUTPUT.PUT_LINE(v_value);
DBMS_OUTPUT.PUT_LINE(v_suit);
END;
/
However i got the error stated in the title and it seems my way of selecting a random card is stopping me from doing an update. Thanks in advance!
Here is a different take on the scenario (I did also address your immediate problem in a different answer).
Given that we really are building a card-dealing program (as opposed to working with a test case for a business scenario) I didn't like the TAKEN column. Updating a table column to mark a transitory state seems wrong. What happens when we want to play another game?
The following solution resolves this by populating an array with all the cards in a random order upfront (the shuffle). The cards are dealt by simply taking the next entry off the stack. The package offers a choice of approach for running out of cards: either throw a user-defined exception or just cycle through the deck again.
create or replace package card_deck is
no_more_cards exception;
pragma exception_init(no_more_cards, -20000);
procedure shuffle;
function deal_one
( p_yn_continuous in varchar2 := 'N')
return cards%rowtype;
end card_deck;
/
create or replace package body card_deck is
type deck_t is table of cards%rowtype;
the_deck deck_t;
card_counter pls_integer;
procedure shuffle is
begin
dbms_random.seed (to_number(to_char(sysdate, 'sssss')));
select *
bulk collect into the_deck
from cards
order by dbms_random.value;
card_counter := 0;
end shuffle;
function deal_one
( p_yn_continuous in varchar2 := 'N')
return cards%rowtype
is
begin
card_counter := card_counter + 1;
if card_counter > the_deck.count()
then
if p_yn_continuous = 'N'
then
raise no_more_cards;
else
card_counter := 1;
end if;
end if;
return the_deck(card_counter);
end deal_one;
end card_deck;
/
Here it is in action. Don't use an open LOOP if you set the continuous dealing mode to Y.
SQL> set serveroutput on
SQL>
SQL> declare
2 my_card cards%rowtype;
3 begin
4 card_deck.shuffle;
5 loop
6 my_card := card_deck.deal_one;
7 dbms_output.put_line ('my card is '||my_card.c_suit||my_card.c_value);
8 end loop;
9 exception
10 when card_deck.no_more_cards then
11 dbms_output.put_line('no more cards!');
12 end;
13 /
my card is HA
my card is H7
my card is DJ
my card is CQ
my card is D9
my card is SK
no more cards!
PL/SQL procedure successfully completed.
SQL>
You may think I'm not dealing with a full deck. You wouldn't be the first to think that ;)
You are using an explicit cursor already, so you don't need the ROWNUM = 1 filter. Try this:
create or replace procedure j_prc_sel_card(p_value OUT number,
p_suit OUT number)
AS
CURSOR CUR_GET_RAND_CARD IS
SELECT c_value,
c_suit,
taken
FROM jackson_card
WHERE taken != 'Y'
ORDER BY dbms_random.value
FOR UPDATE OF taken;
BEGIN
OPEN CUR_GET_RAND_CARD;
FETCH CUR_GET_RAND_CARD into p_value, p_suit;
UPDATE jackson_card
SET taken = 'Y'
WHERE CURRENT OF cur_get_rand_card;
CLOSE CUR_GET_RAND_CARD;
END;
Note the use of WHERE CURRENT OF. This is the most efficient way of locating a row when we are using the FOR UPDATE CLAUSE. Without the use of the NOWAIT clause the cursor will hang if the chosen card is locked by another session. An unlikely scenario perhaps but one worth considering when you move beyond card games and into real scenarios.
Also, remember that for a truly random shuffle you need to call DBMS_RANDOM.SEED() at the start of proceedings.