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

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.

Related

i have two table with foreign key and i want to press in foreign key in first table and the second table appear data

I have two blocks. First one is wizard and second is manual in Oracle Forms 11g with relational.
I execute the data into first block and I want to press in foreign key in first table and the second table appear data which is link together by the same number foreign key?
Which trigger should I use please? And which code should I put into trigger?
You can use WHEN-NEW-RECORD-INSTANCE trigger at block level of Block1 with code :
declare
v_skulist table1.skulist%type;
begin
v_skulist := :Block1.f_skulist; --> represents left uppermost field
go_block('Block2');
execute_query;
go_block('Block1'); --> go back to the upper block again, if don't want to come back, then starting from this line upto `end;`(exclusive) should be removed.
while v_skulist != :Block1.f_skulist
loop
next_record;
end loop;
end;
where
Query Data Source Name property is set to table1 for Block1
and
Query Data Source Name property is set to myuser1.table2 with
WHERE Clause set to
skulist = :Block1.skulist for Block2
assuming the second table is on the other user at least
with granted select privilege to your current user as mentioned in your comment.
This way, whatever record touched in the first block, the counterpart foreign key column is brought in the second block.
You should have used the data block wizard which creates all necessary triggers and procedures that take care about it.
If you're going to do it manually, well ... you probably shouldn't do that, there are too many procedures involved.
I suggest you remove detail block and create it from scratch, this time using the wizard. Pay attention to what it asks and - once you're done - the form will automatically do what you want it to.
Then, you can review what objects Forms created for you and - if you really REALLY want to do it manually, try to mimic its steps.

Oracle Apex 18 shuttle item show last selected values on right side

I have implemented Shuttle List Successfully Using Dynamic Action with Below Code
declare
tab apex_application_global.vc_arr2;
begin
tab := apex_util.string_to_table (:P14_NEW_1);
for i in 1..tab.count loop
insert into xxtest (COL1, COL2)
values (:P14_NEW, tab(i));
end loop;
commit;
end;
Problem is that each the time user open the forms it don't show the last selected values to Right Hand Side, I didn't understand this logic how can i show selected items as saved In table to Right side when page loads
You did the first part of job - stored selected values into a table.
The next step is to retrieve them back. In order to do that,
navigate to shuttle item's source property
set its type to "SQL Query (return colon separated values)
SQL query should look like this:
select listagg(col2, ':') within group (order by null)
from xxtest
where ... --> include condition, if there is any - I believe it should
set "Used" property to "Always, replacing any existing value in session state
Run the page; shuttle item should now have populated right hand side.
As of the where clause: I don't know what is the purpose of doing what you're doing, but - if you don't distinguish rows stored into the xxtest table, all users will use the same recordset and overwrite each other's data. If xxtest.col1 represents username (so that wouldn't be :P14_NEW but :APP_USER instead), you should use it in WHERE clause. Otherwise, consider doing that.

how to display single record in tabular form?

I want to display single record one by one when button press in tabular form
how to do this in oracle forms?
I'd suggest you to use Forms built-in capabilities and spend time and energy on something else.
i want to display single record when press button single forward and all records when press button double forward
I presume that those buttons ("single" and "double forward") are custom-made buttons (i.e. you created them).
If so, "Double forward" is simple - put EXECUTE_QUERY into its WHEN-BUTTON-PRESSED trigger (assuming that this is a data block, based on a table).
"Single forward" isn't that simple as you can't execute query, so you have to write some code. For example, you could "prepare" result data set into another table, specifying the row number so that you could later use it:
insert into temp_table (rn, deptno, dname, loc)
select rownum, deptno, dname, loc
from dept
where ...
WHERE clause is kind of tricky because user can enter search criteria. How to know it? Use GET_BLOCK_PROPERTY. As it brings scent of dynamic SQL, you'd have to compose the INSERT statement - consider creating a stored procedure which utilizes EXECUTE IMMEDIATE (or, alternatively, see if FORMS_DDL can be used).
As many users can use the same form simultaneously, that "temp table" should be a global temporary table or - if not - you'd have to save user information as well; otherwise, you'll create a chaos.
Suppose data is now prepared. In a form, you'd have to create a parameter (or a global variable) which holds current "row number" (rn) value. "Single forward" button's trigger would then
last_record;
create_record;
select deptno, dname, loc
into :blk.deptno, :blk.dname, :blk.loc
from temp_table where rn = :parameter.rn;
-- prepare the next row
:parameter.rn := :parameter.rn + 1;
OK, now you have your data, and new problems arrive. As it is a data block, inserting values into it in that manner presumes that you inserted brand new set of information, and Forms will treat it like that. So, if you try to save it, you'll get a unique key violation (which is OK) or duplicate data (which is bad).
It means that "data block" should, actually, be a "control block", which isn't related to a table. Furthermore, it means that "Double forward" can't work the way I described, but by modifying "Single forward" button's code (don't fetch row-by-row, but all of them at once).
If it is a control block, now you have to find a way to store modified values as well as newly added records, so you'd have to create your own ON-INSERT, ON-UPDATE, etc. triggers.
Shortly, I don't think that what you want is a simple task. Did I already say that you shouldn't do it in the first place? Well, I still think so.
Hopefully, someone else will have a better, simpler suggestion.

Refresh a Classic Report based on a collection

I am currently learning apex but got stuck.
I am using Oracle apex 4.2. My app consists of 2 reports. In first I am choosing some checkboxes and clicking button which fires DA with some js code and uses apex.server.process with AJAX Callback like in this link.
Next the "On Demand" process launches PL/SQL procedure (I know there is no reason for that, but I did it for learning purpose) which processes checked values and concatenates them into one clob which is then being added to collection.
So each time I am clicking the button, exactly 1 new clob is being added.
My second report just reads ALL clobs from collection, and I want to refresh it each time after I click the button without submitting all of the page content. I have added DA with a True Action: Refresh but it works with delay. When I first click it - nothing happens, on the second click the clob which was generated previously shows up in the report region and so on. I guess I need to submit collections value to session state but I have nothing to add to the reports "Page Items to Submit" option.
Not seeing "most recent" data
The only reason you would see "outdated" data is because you use an ajax call to update the collection. It's asynchronous!
Similar, the refresh is async too. The actions don't wait for eachother to complete.
It can go kind of like this:
collect the checkboxes and send them off to some process, which somewhere down the line will return me something
sometimes, it depends, the ajax call is fast enough to have finished before the region refresh occurs
refresh the region
other times, the ajax call has taken a bit longer, and the refresh has already occured
To solve this, you'll have to add something to your apex.server.process call: the refresh of the collection-based report should only occur when the callback has completed.
Firstly though: add a static report id to your collection-based report. Do this by editing it. Providing a static id allows for easier selection of the region through a selector.
apex.server.process('Process Checked Rows', {f01: arr_f01}).done(function(){
//do something when the callback has finished
//like triggering the refresh event on the report required
apex.jQuery("#your_report_static_id_goes_here").trigger("apexrefresh");
});
Now the report will only refresh when you're sure the checkboxes have been processed into the collection.
Using a clob for storing IDs
As I've pointed out in the comments, it makes no sense at all to use a clob to keep the selected IDs together. There's just no argument to make for it beside perhaps pure experimentation. Honestly, time better spent elsewhere if you want to experiment with CLOBs.
Collections are meant to be a Global Temporary Table replacement within the context of Apex sessions. Use them like you would use a table. Simply insert the IDs of the selected rows into the collection.
FOR i IN 1..apex_application.g_f01.count
LOOP
apex_collection.add_member
( p_collection_name => 'CHECKBOX_COLLECTION'
, p_n001 => apex_application.g_f01(i) --NOTE: using N001 here to store a numerical ID
);
END LOOP;
If you only want to have uniques then you can do that here by adding a select on the collection, or add a distinct in the query used to loop or select over the collection.
FOR i IN 1..apex_application.g_f01.count
LOOP
DECLARE
l_exists BINARY_INTEGER;
BEGIN
SELECT 1
INTO l_exists
FROM apex_collections
WHERE collection_name = 'CHECKBOX_COLLECTION'
AND n001 = apex_application.g_f01(i);
EXCEPTION WHEN no_data_found THEN
apex_collection.add_member
( p_collection_name => 'CHECKBOX_COLLECTION'
, p_n001 => apex_application.g_f01(i) --NOTE: using N001 here to store a numerical ID
);
END;
END LOOP;
You can then eeasily loop over the records in this collection or use it to limit rows in other queries (eg another report).
SELECT n001 selected_id
FROM apex_collections
WHERE collection_name = 'CHECKBOX_COLLECTION';

Oracle Apex - Updating a view with instead-of trigger

Apex beginner here. I have a view in my Oracle database of the form:
create or replace view vw_awkward_view as
select unique tab1.some_column1,
tab2.some_column1,
tab2.some_column2,
tab2.some_column3
from table_1 tab1,
table_2 tab2
WHERE ....
I need the 'unique' clause on 'tab1.some_column1' because it has many entries in its underlying table. I also need to include 'tab1.some_column1' in my view because the rest of the data doesn't make much sense without it.
In Apex, I want to create a report on this view with a form for editing it (update only). I do NOT need to edit tab1.some_column1. Only the other columns in the view need to be editable. I can normally achieve this using an 'instead-of' trigger, but this doesn't look possible when the view contains a 'distinct', 'unique' or 'group by' clause.
If I try to update a row on this view I get the following error:
ORA-02014: cannot select FOR UPDATE from view with DISTINCT, GROUP BY, etc.
How can I avoid this error? I want my 'instead-of' trigger to kick in and perform the update and I don't need to edit the column which has the 'unique' clause, so I think it should be possible to do this.
I think that you should be able to remove the "unique".
if tab2.some_column1, tab2.some_column2, tab2.some_column3 are not unique, then how do you want to update them ?
if they are unique then the whole result: tab1.some_column1, tab2.some_column1, tab2.some_column2, tab2.some_column3 is unique.
When you state in a sql query "unique" or "distinct" it's for all columns not only 'tab1.some_column1'
Hope i'm in the correct direction of your question here ;)
Your query could be achieved by doing something like:
select a.some_column1, tab2.some_column1, tab2.some_column2, tab2.some_column3
from table_2 tab2
join (select distinct some_column1 from table_1) a
on tab2.column_in_tab1 = a.some_column1
The reason you get the ORA-02014 error is because of the automatically generated ApplyMRU process. This process will attempt to lock a (the) changed row(s):
begin
for r in (select ...
from vw_awkward_view
where <your first defined PK column>= 'value for PK1'
for update nowait)
loop
null;
end loop;
end;
That's a bummer, and means you won't be able to use the generated process. You'll have to write your own process which does the updating.
For this, you'll have to use the F## arrays in apex_application.
If this sounds totally unfamiliar, take a look at:
Custom submit process, and on using the apex_application arrays.
Also, here is a how-to for apex from 2004 from Oracle itself. It still uses lots of htmldb references, but the gist of it is there.
(it might be a good idea to use the apex_item interface to build up your form, and have control over what is generated and what array it takes.)
What it comes down to is: loop over the array containing your items and do an UPDATE on your view with the submitted values.
Of course, you don't have locking this way, nor a way to prevent unnecessary updates.
Locking you can do yourself, with for example using the select for update method. You'd have to lock the correct rows in the table(s) you want to alter, before you update them. If the locking fails, then your process should fail.
As for the 'lost update' story: here you'd need to check the MD5-checksums. A checksum is generated from the editable columns in your form and put in the html-code. On submit, this checksum is then compared to a newly generated checksum from those same columns, but with values from the database at that time of submit. If the checksums differ, it means the record has changed between the page load and the page submit. Your process should fail because the record has been altered, and you don't want to have those overwritten. (if you go the apex_item way, then don't forget to include an MD5_CHECKSUM call (or MD5_HIDDEN).
Important note though: checksums generated by either using apex_item or simply the standard form functionality build up a string to be hashed. As you can see in apex_item.md5_hidden, checksums are generated using DBMS_OBFUSCATION_TOOLKIT.MD5.
You can get the checksum of the values in the DB in 2 ways: wwv_flow_item.md5 or using dbms_obfuscation.
However, what the documentation fails to mention is this: OTN Apex discussion on MD5 checksums. Pipes are added in the generated checksums! Don't forget this, or it'll blow up in your face and you'll be left wondering for days what the hell is wrong with it.
Example:
select utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string=>
"COLUMN1" ||'|'||
"COLUMN2" ||'|'||
"COLUMN5" ||'|'||
"COLUMN7" ||'|'||
"COLUMN10" ||'|'||
"COLUMN12" ||'|'||
"COLUMN14" ||
'|||||||||||||||||||||||||||||||||||||||||||'
)) md5
from some_table
To get the checksum of a row of the some_table table, where columns 1,2,5,7,10,12,14 are editable!
In the end, this is how it should be structured:
loop over array
generate a checksum for the current value of the editable columns
from the database
compare this checksum with the submitted checksum
(apex_application.g_fcs if generated) if the checksums match,
proceed with update. If not, fail process here.
lock the correct records for updating. Specify nowait, and it
locking fails, fail the process
update your view with the submitted values. Your instead-of trigger
will fire. Be sure you use correct values for your update statement so that only this one record will be updated
Don't commit inbetween. It's either all or nothing.
I almost feel like i went overboard, and it might feel like it is all a bit much, but when you know the pitfalls it's actually not so hard to pull this custom process off! It was very knowledgable for me to play with it :p
The answer by Tom is a correct way of dealing with ths issue but I think overkill for your requirements if I understand correctly.
The easiest way may be to create a form on the table you want to edit. Then have the report edit link take the user to this form which will only update the needed columns from the one table. If you need the value of the column from the other table displayed it is simple when you create the link to pass this value to the form which can contain a display only item to show this.

Resources