Oracle APEX; changing values after update/insert - oracle

I am developping a web application in Oracle Application Express (Apex) technology.
I have a page where I can add a new record to a table called 'tz_tab'. That table has a column 'pos' which is one letter (single char) - 'Y' or 'N'. Now, i'd like to do something like this:
When post processing (== updating/inserting 'tz_tab') form on the page - check if 'pos' value is 'Y'
If so - update the rest of records in 'tz_tab' values to 'N'
I tried creating triggers on 'tz_table' but with no success.
Summing up, I need to have only one record with 'pos' value == 'T' in 'tz_tab'.
Is there any method to do this by APEX or should it be done with oracle triggers?
Thanks!

It sounds like you can achieve this by creating a PL/SQL process in your APEX page that will fire after submit. You should be able to check page items and perform an UPDATE statement as required.

You don't say how you're doing your processing in Apex, but you can create a page process that fires after your insert/update step with something like.
UPDATE tz_table SET pos = 'N' WHERE id != :ID_OF_THE_ITEM_JUST_UPDATED

Related

How to Make Form w/ No underlying DB Table - APEX 19.2

Very noob question, but how can I create a form in APEX that is not based on a database table?
For example, I am looking to create a "Change Password" form that consists of 3 fields:
USERNAME (display only, value populated with &APP_USER. from URL)
NEW_PSWD
CONFIRM_PSWD
None of these fields will be saved to the database, so not sure how to handle the SOURCE for the underlying form. My first thought was to just use a SQL statement like this:
SELECT '' as username,
'' as new_pswd,
'' as confirm_pswd
FROM DUAL;
Is there a better / more APEX way to handle this?
UPDATE: I understand how to set up a process to handle the logic, but more asking what should I select for the SOURCE? It "yells" at me if I select a TYPE (TABLE/VIEW, SQL QUERY, PLSQL FUNCTION BODY...) and do not put something in there.
Thanks in advance.
Don't use "Form" page type. Pick "Blank" instead.
Then
create a region on it
put any number of items (3 in your case)
create a button
create a process which will fire when you press that button
it should do "smart" things in the database - insert user into a table, change their password, whatever you plan to do

How does bind variable substitution work for page Items in ORACLE APEX

I was wondering how does ORACLE APEX identify if a certain page item value should go as number or varchar2 when used in stored procedure call.
my_pkg.proc1(P1 =>:P2_ITEM1);
Here, how does APEX engine identify if the parameter P1 for proc1 is expecting a number or varchar2 or date.
To extend:
I have a grid. With query as below
Select * from table(my_pkg.pipe_func(:P2_PARAM1,:P2_PARAM2));
When user clicks a button I need to get the current session state value of these bind substitutions and I want to store above select query in a table as
Select * from table(my_pkg.pipe_func('ParamVal',256));
How can this be achieved?
It's the Oracle database that does this grunt work, in the same way it's done it for years. You can see this in the v$sqlarea.
And you should explicity convert these bind variables, as they will be inheretly treated as a varchar - same as they're stored.
where id = to_number(:p1_id)
And from an APEX perspective, if the user changes a value on screen, and you invoke a dynamic action to refresh a region using those items - you should list them in the 'Page items to submit' attribute, just under the region SQL. This will ensure the database session state is updated with what's in the browser, before conducting the refresh.

Making a search bar manually WITHOUT interactive report. (Oracle APEX)

I am trying to make an application completely manually without using any interactive reports or generated PL/SQL. Everything is working at the moment but I am stumped when it comes to the search bar and I can't find anything online to help me.
I have a classic report called 'Browse Job Vacancies' and a 'Search' button; I also have the 'C1_JOB_TITLE_ITEM' search bar and a page process called 'Search'.
In the process I have this code:
SELECT
JOB_CODE,
JOB_TITLE,
JOB_DESCRIPTION,
SITE_NAME,
EMAIL_ADDRESS,
TELEPHONE_NUMBER,
SALARY,
START_OF_PLACEMENT,
APPLICATION_METHOD,
APPLICATION_CLOSING_DATE
FROM JOB
WHERE upper(job_title) = upper(:C1_JOB_TITLE_ITEM);
I am getting this error:
ORA-06550: line 1, column 64: PLS-00428: an INTO clause is expected in this SELECT statement
So I created this code:
DECLARE temp_row char;
BEGIN
SELECT
JOB_CODE,
JOB_TITLE,
JOB_DESCRIPTION,
SITE_NAME,
EMAIL_ADDRESS,
TELEPHONE_NUMBER,
SALARY,
START_OF_PLACEMENT,
APPLICATION_METHOD,
APPLICATION_CLOSING_DATE
INTO temp_row
FROM JOB
WHERE job_title = :C1_JOB_TITLE_ITEM;
END;
Here I am getting this error:
ORA-06550: line 13, column 16: PL/SQL: ORA-00947: not enough values
I am completely stumped on what to do at this point so any help at all is greatly appreciated. Sorry if I'm not being detailed enough this is my first time writing in Stack Overflow.
(Editing because someone didn't think this was an answer.)
You say you have a page process. You don't need any sort of process for this. You should have a Region with the following properties (assuming 18.x):
Type = Classic Report
Source > Location = Local Database
Source > Type = SQL Query
Source > SQL Query > your query from your first block above
Apex will run the query and create a report based on it. Processes are for PL/SQL blocks that do something like populating data or manipulating the database in some way.
When you enter a keyword in your item and click your button (the button action should be Submit Page), Apex will populate C1_JOB_TITLE_ITEM in session state with the user's value, then use that in the bind variable when it re-generates the page.
1) Create a classic report with that SQL as the source code.
2) Set C1_JOB_TITLE_ITEM in Page Items To Submit attribute, just under region source.
3) Create the page item C1_JOB_TITLE_ITEM, as your search field.
4) Create a Dynamic Action on Change of that item, and refresh your classic report region. The timing of this refresh is up to you. Step 2 ensures the value you've typed in the browser is sent to the database for the purpose of re-running that query.
Do you have a function based index on upper(job_title)? Otherwise you may experience performance issues.

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 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