In Oracle Forms 10g, I have the following code in WHEN-VALIDATE-RECORD trigger.
if(some_condition > 0) then
message('test');
RAISE FORM_TRIGGER_FAILURE;
end if;
Problem is message('test'); appears multiple times. How can I make sure it appears only once.
The trigger WHEN-VALIDATE-RECORD will go off for the record that needs to be validated after leaving the record or pressing commit.
In your case I assume the message appears after a commit and you changed all your rows or at least more then one in for example the post-query trigger.
Because more then one row is changed the trigger will fire for all of these rows and you will get the message multiple times.
Try just after you query your records without changing anything to commit.
It should say there is nothing changed to commit. If it just commit for example 10 rows then this is your problem.
Related
You are an awesome comunity. It is the first time I couldn't find an answer for my questions and I had millions
Take me easy. I'm super noob and I already fell bad for making you waste your time with my dummy question
Soo... I'm tring to make an app with oracle apex. I have an form with an interactive report for table1. On the form page I have 3 processes in this order:
Automatic Row Processing (DML) that apex automaticaly made for me,
a pl/sql process I made and
the reset page process apex made.
The ARP updates, creates and deletes and is triggered by any of the buttons (SAVE, CREATE, DELETE).
My procces deletes a row in another table2 and is performed when DELETE is clicked and ITEM1 is not null (because in ITEM1 I stored the PK for the row in the second table).
The last process is the usual reset page that should clear all items value when DELETE is pressed.
Firing point is by default "Processing" for all 3.
Sometimes my process fails (and return the error I set) because of a FK constraint.
Now here is the think: If my proccess fails, the oder 2 seem not to be executed. Is that posible? If i set the condition (to be executed) of my process to Never the other 2 are working. What am I missing?
You aren't missing anything.
When you push a button that fires those processes, they make a transaction. If any of them raises an error, all of them (executed so far) are rolled back.
If you want to continue processing regardless what your own procedure (2nd one) does (I mean: whether it succeeds or not), then handle it, somehow.
A trivial (and not the best) option is to ignore possible errors, e.g.
begin
delete from child_table where id = :P1_ITEM1;
exception
when others then null; --> ignore any errors
end;
Smarter way would be to intercept errors you expect. If you know (and yes, you do) that there's a possibility that foreign key constraint will be violated, check whether child rows exist; if not, delete the master row.
declare
l_id child_table.id%type;
begin
-- If row(s) with such an ID exists, L_ID will be set to that value.
-- In that case, don't do anything
select m.id
into l_id
from child_table m
where m.id = :P1_ITEM1
and rownum = 1;
-- The above query returned something; don't do anything
null;
exception
when no_data_found then
-- The above query didn't return anything, so - delete a row
delete from child_table where id = :P1_ID;
end;
Now, that can/could/should be modified, depending on what you really have; it is just an idea what to look at.
Yet another option is to set foreign key constraint to be on delete cascade, which means that deleting master record automatically deletes its detail records. Doing so, you wouldn't care about such a problems and your 2nd process would be as simple as
delete from child_table where id = :P1_ID;
(unless you hit another kind of an error, of course).
If you want to let users decide whether they want to delete rows or not, change button's action to "Redirect to URL" (currently it is "Submit", I presume). The target URL will be something like this (suppose that button's name is P1_START_PROCESSES):
javascript:if(confirm('Are you sure you want to delete all rows related to this document?')){doSubmit('P1_START_PROCESSES');}
When user begin filling the record then they click other record before committing i want to clear the last record like it never filled.
So i create when-validate-record in data block as below:
begin
if :MYBLOCK.SEQ is null then --if there is no seq yet, means its not committed
Clear_Record;
end if;
end;
But clear_record causes error that says Invalid limited CLEAR_RECORD procedure and usage seems correct to me. And i also tried Clear_Record(no_validate) but it also doesn't work. I saw some usage of clear_record and i can't get why it doesn't work. I could use some help.
Thanks in advance.
Restricted procedures (not limited, as you put it) can't be called from all triggers; WHEN-VALIDATE-RECORD being one of them.
One option you might choose is to interact with the user and tell them what to do. Still the WHEN-VALIDATE-RECORD trigger:
if :MYBLOCK.SEQ is null then -- if there is no seq yet, means its not committed
message('Save the record or - if you do not need it - delete it');
raise form_trigger_failure;
end if;
Benefit of such an approach is that user (not the procedure in background) decides what to do. I wouldn't want to enter 20 fields on the screen, accidentally click one of previous records and lose everything I've done so far.
I tried to create code using Oracle trigger syntax, but there is a warning:
trigger created with compilation errors
Here is the code. I want to raise an error when the price in table itemtype is set to over four times the minimum old price. How can I do that?
I tried to fix the previous problem! I created the trigger successfully, but it only works when insert, the "mutating table" error still occurs when I was tring to update the price on table itemType. How can I change my code to make it also works when updating?
CREATE or replace TRIGGER tr_price before insert or update on itemType for each row
declare minimum float;
begin
select min(price)
into minimum
from itemType;
if :new.price > 4*minimum
then
raise_application_error (-20000,'new price can not over 4 times min old price');
END IF;
end;
/
As clarified in the Comments (or, if you answer my request, as edited in the post itself), you are running into the "mutating table" error. And you would like to know how to fix it.
Alas, this is not a programming error. It is a logical problem (which Oracle chose not to ignore, by rising the "mutating table" error).
Oracle is a multi-user environment, and SQL allows you to insert/alter/delete many rows in one transaction. Both of these things are GOOD, but they mean you can't do things like what you are trying to do.
What is the "old" minimum price? Suppose you had 20 rows already, and the minimum price was $55. You add one more row with the price $50. Now you try to add another row with price $210. What is "the old minimum"? $220? Why, because the row with the price of $50 was inserted, but not committed yet? Or is it $200? What if, in the same transaction, but later in your code, you DELETE the row with the $55 price and the next lowest is $60 - shouldn't you allow prices up to $240?
Then compound this problem: you insert a few rows, but you don't commit yet. Then someone else inserts (or updates or deletes) in the same table, and they commit their transaction. Now you want to commit. Shouldn't the "check" be performed again, at the end of the transaction, and not "for each row"?
The whole idea of tying the behavior of DML statements to data in the table, as it exists at one time or another (and as may change in the middle of your transaction, as caused either by your own other activity or by the activity of other users at the same time), is something you must understand clearly, and discuss with the "business managers" or "business users" who may not know or understand SQL, but who must understand this issue and who must come up with logical requirements. Only allowing inserts where the price is "no more than four times the old minimum" is not internally consistent, and Oracle, wisely, does not allow you to do something that wouldn't make sense.
So - the short answer is "you can't fix the solution, because the solution is not broken, the problem itself is broken." You must fix the problem first.
I have a table of people who belong to various sites. These sites can change, but don't very often. So when we create an attendance record (a learner_session object) we don't store the site. But this has cause a problem in reporting how many training hours a site has, because some people have changed sites over the years. Not by much, but we'd like to get this right.
So I've added a site_at_the_time column to the learner_session table. I want to auto-populate this with the site the person was at when they attended the session. But I'm not sure how to reference this. For some reason (I'm guessing to speed development or something) the learner_id is allowed to be null. So I'm currently planning to do an update trigger. The learner_id shouldn't ever get updated, and if it ever did somehow, the entire record would be junk so I'm not worried about it overwriting it.
The trigger I have now is
create trigger set_site_at_the_time
after update of learner_id on lrn_session
begin
:new.site_at_the_time:= (select site_id from learner who where :new.learner_id = who.learner_id);
end;
which leads me to the following error:
ORA-04082: NEW or OLD references not allowed in table level triggers
Now, I've done some research and found I need to use a FOR EACH ROW - and I'm wondering what exactly this FOR EACH ROW does - is it every row captured by the trigger? Or is it every row in the table?
Also, will this trigger when I create a record too? So if I do insert into learner_session(id,learner_id,...) values(learner_session_id_seq.nextval,1234,...) will this capture that appropriately?
And while I'm here, I might as well see if there's something else I'm doing wrong with this trigger. But I'm mainly asking to figure out what the FOR EACH ROW is supposed to do and if it triggers properly. =)
FOR EACH ROW means that the trigger will fire once for each row that is updated by your SQL statement. Without this clause, the trigger will only fire once, no matter how many rows are affected. If you want to change values as they're being inserted, you have to use FOR EACH ROW, because otherwise the trigger can't know which :new and :old values to use.
As written, the trigger only fires on update. To make it also fire upon insert, you'd need to change the definition:
CREATE TRIGGER set_site_at_the_time
BEFORE INSERT OR UPDATE OF learner_id
ON lrn_session
FOR EACH ROW
BEGIN
SELECT site_id into :new.site_at_the_time
FROM learner who
WHERE :new.learner_id = who.learner_id);
END set_site_at_the_time;
I googled this but, unfortunately, could not find any solution.
I have a simple form (Oracle Forms Builder 10g) with a single block. The form is written in Oracle EBS style, that is, the block is based on a view that fetches the base table fields together with the rowid and DML events (on-insert, on-update etc. triggers) are handled by a table handler package.
The functionality I wanted to add was the following: when a user creates a new record, the form automatically suggests values for all fields in the form. So, I created a WHEN-CREATE-RECORD trigger that calculates the field values and assigns them. All, except the primary key wich is based on a Sequence and is handled by the package.
Everything runs OK when I create the new record but when I attempt to save it, all I get is a FRM-40401 "no changes to save" error and nothing happens.
I tried to trace the error and it seems like the form considers the record as NEW with no changes on it. This happens even if I try to explicitly alter the record status to INSERT.
I already tried to change the default behaviour to STANDARD.COMMIT (created an ON-COMMIT trigger for that) but this did not dfix anything.
For the record, I tried to make the form table-based, getting rid of table handlers and leaving all DML to Forms. I still get FRM-40401.
I can't understand what is going wrong, any ideas please?
The record status is reset to NEW after the when-create-record trigger completes. This normally makes sense, since you are effectively setting the default values for items, but the user hasn't actually entered any data yet.
You need something to mark the record for insert after the trigger has finished - normally the user would do this when they enter some data into the record. If you want the user to be able to save the record without changing anything in it, you could perhaps add something to the save button to do a no-change assignment, e.g.
:MYBLOCK.ANYITEM := :MYBLOCK.ANYITEM;
This would cause the record to be marked for insert.
OK, for the time being I used the classic TIMER workaround and everything works as it should:
PACKAGE body form_timers IS
PROCEDURE CREATE_NEW_RECORD;
procedure do_create(name varchar2) is
timer_id TIMER;
Begin
timer_id := CREATE_TIMER(name,1,NO_REPEAT);
End;
procedure expired is
expired_timer CHAR(20);
BEGIN
expired_timer:=GET_APPLICATION_PROPERTY(TIMER_NAME);
IF expired_timer='CREATE_NEW_RECORD' THEN
CREATE_NEW_RECORD;
-- ELSIF expired_timer='T2' THEN
-- /* handle timer T2 */ NULL;
ELSE
NULL;
END IF;
END;
PROCEDURE CREATE_NEW_RECORD IS
/* create record logic goes here */
END;
END;
... but still, I'd like to know why this behaviour occurs.