Update table using multi record block in post forms commit - oracle

what I wanted is to update my table using values from a multi record block and here is what I tried in post forms commit:
BEGIN
FIRST_RECORD;
LOOP
UPDATE table1
SET ord_no = :blk.new_val;
EXIT WHEN :SYSTEM.LAST_RECORD='TRUE';
NEXT_RECORD;
END LOOP;
END;
but when I save I got an error
FRM-40737: Illegal restricted procedure
FIRST-RECORD in POST-FORMS-COMMIT trigger.

OK, a few things to talk about here
1) I'm assuming that 'table1' is NOT the table on which the block is based. If the block was based on table1, simply as the user edits entries on screen, then when you do a COMMIT_FORM command (or the users clicks Save) then the appropriate updates will be done for you automatically. (That is the main use of a database-table based block).
2) So I'm assuming that 'table1' is something OTHER than the block based table. The next thing is that you probably need a WHERE clause on your update statement. I assume that you are updating a particular in table1 based on a particular value from the block? So it would be something like:
update table1
set ord_no = :blk.new_val
where keycol = :blk.some_key_val
3) You cannot perform certain navigation style operations when in the middle of a commit, hence the error. A workaround for this is to defer the operation until the completion of the navigation via a timer. So your code is something like:
Declare
l_timer timer;
Begin
l_timer := find_timer('DEFERRED_CHANGES');
if not id_null(l_timer) then
Delete_Timer(l_timer);
end if;
l_timer := Create_Timer('DEFERRED_CHANGES', 100, no_Repeat);
End;
That creates a timer that will fire once 100ms after your trigger completes (choose name, and time accordingly) and then you have your original code on a when-time-expired trigger.
But please - check out my point (1) first.

Related

Oracle automatically insert record in multirecord block part 2

My table looks like this:
+-------------------+
|Name |
+-------------------+
|Name1 |
|Name2 |
|Name3 |
|Name4 |
|Name1Jr |
|Name2Jr |
|Name4Jr |
+-------------------+
My multirow block looks like:
What I wanted to know is how can I insert a record that has the same name with Jr after I insert a name. For example, I inserted Name2, it will also insert Name2Jr into the multirow block. Like this:
Note: I need to get the value of the automatically inserted data from database.
I tried in WHEN-NEW-RECORD-INSTANCE trigger(the answer of Sir #Littlefoot on my last question):
if :system.trigger_record = 1 and :test.name is null then
-- do nothing if it is the first record in a form
null;
else
duplicate_record;
if substr(:test.name, -2) = 'Jr' then
-- you've duplicated a record which already has 'Jr' at the end - don't do it
:test.name := null;
else
-- concatenate 'Jr' to the duplicated record
:test.name := :test.name || 'Jr';
end if;
end if;
And now, what I want is to know if there is a way to do it on WHEN-VALIDATE-RECORD trigger. The problem there is that the duplicate_record can't be used in WHEN-VALIDATE-RECORD trigger. How to do it using procedure, function or something? Thanks in advance!
DUPLICATE_RECORD is a restricted procedure and you can't use it in WHEN-VALIDATE-RECORD trigger (or any other of the same kind).
As you have to navigate to the next record (if you want to copy it), even if you put that restricted procedure into another PL/SQL program unit, everything will just propagate and - ultimately - raise the same error. So ... you're out of luck.
Even if you wrote a (stored) procedure which would insert that "Jr" row into the database somewhere behind the scene, you'd have to fetch those values to the screen. As EXECUTE_QUERY is the way to do it, and as it is (yet another) restricted procedure, that won't work either.
If you planned to clear data block and fill it manually (by using a loop), you'd have to navigate to next (and next, and next) record with NEXT_RECORD, and that's again a restricted procedure. Besides, if it was a data block (and yes, it is), you'd actually create duplicates for all records once you'd save changes so - either it would fail with unique constraint violation (which is good), or you'd create duplicates (which is worse).
BTW what's wrong with WHEN-NEW-RECORD-INSTANCE? What problems do you have when using it?
What We need is
a Data Block (BLOCK1) with
Query Data Source Name is mytable,
and Number of Records Displayed set to more than 1(let it be 5 as in your case).
a Text item named as NAME same as the column name of the table
mytable, and Database Item property set to Yes.
a Push Button with Number of Records Displayed set to 1,
Keyboard Navigable and Mouse Navigate properties are set to No
and has the following code (inside WHEN-BUTTON-PRESSED trigger):
commit;
Query_Block1;
Where Query_Block1 is beneath Program Units node with this code :
PROCEDURE Query_Block1 IS
BEGIN
execute_query;
last_record;
down;
END;
A POST-INSERT trigger beneath BLOCK1 node with code :
insert into mytable(name) values(:name||'Jr');
An ON-MESSAGE trigger at the form level with the code (to suppress the messages after commit):
if Message_Code in (40400, 40401) then
null;
end if;
And WHEN-NEW-FORM-INSTANCE trigger with the code :
Query_Block1;

PL/SQL: ORA 01422 fetch returns more than requested number of rows

I am developing an order transaction where a user can order a product. Once they clicked the 'add to cart' button, it will be able to save on the database in how many times they want with the same order id. Order id is like a transaction id.
My problem is that whenever I want to display the items that customer ordered, it displays an error or ORA 01422. How can I resolve this error?
Here is my code
DECLARE
order_item_id NUMBER;
BEGIN
order_item_id := :MOTOR_PRODUCTS_ORDER.M_ORDERID;
SELECT MOTOR_ID,
MOTOR_QTY_PURCHASED,
UNITPRICE
INTO :MOTOR_ORDER_ITEMS.MOTOR_ID,
:MOTOR_ORDER_ITEMS.MOTOR_QTY_PURCHASED,
:MOTOR_ORDER_ITEMS.UNITPRICE
FROM MOTOR_ORDERS
WHERE motor_order_id = order_item_id;
END;
As krokodilo says, this error is caused because your query returns multiple rows. Depending on what you want to do, you have a couple of options.
If you want multiple values then either use a loop and process them one row at a time (if you are going to be performing a dml operation use bulk collect). If you only want a single row then narrow your result set down with an extra where clause, or use MAX to ensure you only get one value back.
If there is more than one row which will be returned from a query you'll need to use a cursor. One way to do this is with a cursor FOR loop:
DECLARE
order_item_id NUMBER;
BEGIN
order_item_id := :MOTOR_PRODUCTS_ORDER.M_ORDERID;
FOR aRow IN (SELECT MOTOR_ID, MOTOR_QTY_PURCHASED, UNITPRICE
FROM MOTOR_ORDERS
WHERE motor_order_id = order_item_id)
LOOP
-- Do something here with the values in 'aRow'. For example, you
-- might print them out:
DBMS_OUTPUT.PUT_LINE('MOTOR_ID=' || aRow.MOTOR_ID ||
' MOTOR_QTY_PURCHASED=' || aRow.MOTOR_QTY_PURCHASED ||
' UNITPRICE=' || aRow.UNITPRICE);
END LOOP;
END;
Best of luck.
This looks like a Forms question; is it? If so, my suggestion is to let Forms do that job for you.
According to what you posted, there are two blocks:
MOTOR_PRODUCTS_ORDER (a master block, form type)
MOTOR_ORDER_ITEMS (a detail block, tabular type)
I guess that there is a master-detail relationship between them. If there's none, I'd suggest you to create it. Although you can make it work without such a relationship, it'll be much more difficult. If you are unsure of how to do it, start from scratch:
delete MOTOR_ORDER_ITEMS block (detail)
create it once again, this time by following the Data Block Wizard
Set MOTOR_PRODUCTS_ORDER to be its master block
Relationship is on ORDER_ID column/item
Let's presume that by this point everything is set up. Retrieving items that belong to that ORDER_ID is now very simple:
navigate to master block
enter query mode
enter value into an item that represents ORDER_ID
execute query
End of story. Forms triggers & procedures (which were created by the Wizard) will do its job and retrieve both master and detail records.
No need for additional coding; if you're skilled developer, you can create such a form in a matter of minutes. Won't be beautiful, but will be effective & reliable.
There's really no use in doing it manually, although it is possible. Your code works if there's a single item for that ORDER_ID. For two or more items, as you already know, it'll fail with TOO-MANY-ROWS error.
Basically, if you insist, you should use a loop:
you'd enter ORDER_ID into the master block
as you need to move through the detail block, i.e. use NEXT_RECORD, which is a restricted procedure, you can't use number of triggers (open Forms Online Help System and read about them) so a "Show items" button (with its WHEN-BUTTON-PRESSED trigger) might be just fine
a cursor FOR loop would be your choice
for every row it fetches, you'd populate block items and
navigate to next record (otherwise, you'd keep overwriting existing values in the 1st tabular block row)
As I said: possible, but not recommended.

Oracle - Fetch returns more than requested number of rows - using triggers

So I am trying to use triggers to basically set some rules.. If anyone has an ID number lower than 3, he will have to pay only 100 dollars, but if someone has an ID above that, he will have to pay more. I did some research and have been told to use triggers and that triggers are very useful when fetching multiple rows. So I tried doing that but it didn't work. Basically the trigger gets created but then when i try to add values, I get the following error:-
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "S.PRICTICKET", line 6
ORA-04088: error during execution of trigger 'S.PRICTICKET'
here is what i did to make the trigger:-
CREATE OR REPLACE TRIGGER PRICTICKET BEFORE INSERT OR UPDATE OR DELETE ON PAYS FOR EACH ROW ENABLE
DECLARE
V_PRICE PAYS.PRICE%TYPE;
V_ID PAYS.ID%TYPE;
V_NAME PAYS.NAME%TYPE;
BEGIN
SELECT ID,NAME INTO V_ID,V_NAME FROM PAYS;
IF INSERTING AND V_ID<3 THEN
V_PRICE:=100;
INSERT INTO PAYS(ID,NAME,PRICE) VALUES (V_ID,V_NAME,V_PRICE);
ELSIF INSERTING AND V_ID>=3 THEN
V_PRICE:=130;
INSERT INTO PAYS(ID,NAME,PRICE) VALUES (V_ID,V_NAME,V_PRICE);
END IF;
END;
and the thing is, when i execute this code, i actually do get a message saying the trigger has been compiled. but when when i try to insert values into the table by using the following code, i get the error message I mentioned above.
INSERT INTO PAYS(ID,NAME) VALUES (19,'SS');
You're getting the error you specified, ORA-01422, because you're returning more than one row with the following SELECT:
SELECT ID,NAME INTO V_ID,V_NAME FROM PAYS;
You need to restrict the result set. For example, I'll use the :NEW psuedorecord to grab the row's new ID value, which if unique, will restrict the SELECT to one row:
SELECT ID,NAME INTO V_ID,V_NAME FROM PAYS WHERE ID = :NEW.ID;
Here is the Oracle docs on using triggers: https://docs.oracle.com/database/121/TDDDG/tdddg_triggers.htm#TDDDG99934
However, I believe your trigger has other issues, please see my comments and we can discuss.
EDIT: Based on our discussion.
ORA-04088: error during execution of trigger
Using INSERT inside a BEFORE INSERT trigger on the same table will create an infinite loop. Please consider using an AFTER INSERT and change your INSERTS to UPDATES, or an INSTEAD OF INSERT.
Additionally, remove DELETE from the trigger definition. That makes no sense in this context.
Let's begin clearing up a few things. You were told "triggers are very useful when fetching multiple rows" this is, as a general rule and without additional context, false. There are 4 types of DML triggers:
Before Statement - fires 1 time for the statement regardless of the number of rows processed.
Before Row - fires once for each row processed during the statement before old and new values are merged into a single set of values. At this point you are allowed to change the values in the columns.
After Row - fires once for row processed during the statement after merging old and new values into a single set of values. At this point you cannot change the column values.
After statement - fires once for the statement regardless of the number of rows processed.
Keep in mind that the trigger is effectively part of the statement.
A trigger can be fired for Insert, Update, or Delete. But, there is no need to fire on each. In this case as suggested, remove the Delete. But also the Update as your trigger is not doing anything with it. (NOTE: there are compound triggers, but they contain segments for each of the above).
In general a trigger cannot reference the table that it is fired upon. See error ORA-04091.
If you're firing a trigger on an Insert it cannot do an insert into that same table (also see ORA-04091) and even if you get around that the Insert would fire the trigger, creating a recursive and perhaps a never ending loop - that would happen here.
Use :New.column_name and :Old.column_name as appropriate to refer to column values. Do not attempt to select them.
Since you are attempting to determine the value of a column you must use a Before trigger.
So applying this to your trigger the result becomes:
CREATE OR REPLACE TRIGGER PRICTICKET
BEFORE INSERT ON PAYS
FOR EACH ROW ENABLE
BEGIN
if :new.id is not null
if :new.ID<3 then
:new.Price :=100;
else
:new.Price := 130;
end if ;
else
null; -- what should happen here?
end if ;
END PRICTICKET ;

Oracle forms: Rollback an item to database value without changing form

Let's say if some condition is fullfilled, it shouldn't be possible to make any change to my entire block. I've found a simple way to do this:
In the 'WHEN-VALIDATE-ITEM' trigger on blocklevel, I've written this statement:
begin
if (-- custom statement --
and :system.record_status <> 'QUERY'
and get_item_property(:system.current_item,DATABASE_VALUE) <> name_in(:system.current_item)) then
msgbox('Can''t change');
copy(get_item_property(:system.current_item,DATABASE_VALUE), :system.current_item);
raise form_trigger_failure;
end if;
end;
The copy statement sets the item value back to database value. But the problem is, that the status of my form will go to 'CHANGED', while there actually is not really a change visible, since the item got rollbacked to the database value. So when I try to exit the form, the system asks me if I want to save my changes. And I don't want that to happen. How can I change this?
To avoid asking to User if want to save or not values, you can perform Key-Commit
BEGIN
EXECUTE_TRIGGER('KEY-COMMIT');
standard.commit;
END;

Oracle APEX Selection list

I have a form on a table with some values (text fields) and a select list. The select list is declared in shared components and shows me values from another table. Also I have some process (after submit) to modify and create new table entry. Everything works fine without propagating values from select list. There are no errors from the process. It looks like the process didn't get a value for :P22_WORKER_LIST from the selection list. It should work when I push the create or save buttons but nothing happened. Every instruction in BEGIN END block runs great without this one.
Process:
BEGIN
<some instructions>
UPDATE "WORKER" SET ACCOUNT_LOGIN = :P22_LOGIN
WHERE SURNAME = :P22_WORKER_LIST;
END;
Thank you for suggestion with the session state. After submiting page it turned out that the value for my :P22_WORKER_LIST is a WORKER_ID instead a SURNAME.
BEGIN
<some instructions>
UPDATE "WORKER" SET ACCOUNT_LOGIN = :P22_LOGIN
WHERE WORKER_ID = :P22_WORKER_LIST;
END;

Resources