Column Validation not firing in Interactive Grid Oracle APEX - oracle

I'm developing a simple application in Oracle APEX and one of the requirements is to create an error message if a duplicate column (which is also the UK of the table) is saved to the Interactive Grid. I've created a support package and then a column validation in APEX that calls the function in the support package.
When I first implemented the code and the call, everything was working and the correct error message was displayed. However, I think I unknowingly modified a property setting or something because now I cannot get the validation to fire -- if the user enters a duplicate column value and presses save, they get the generic "constraint violation" error message that Oracle raises. The only IG Process in this app is the Automatic Row Processing - Save button.
Does anyone have know why the column validation is being ignored? I have gone through the error stack of the "constraint violation" and all I am seeing is the Save process and Oracles generic error messages.
Support Package Function:
`FUNCTION Prod_Family_Exists (
i_id IN NUMBER
,i_prod_family IN VARCHAR2
) RETURN BOOLEAN IS
v_cnt NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_cnt
FROM adm_prod_families
WHERE prod_family = i_prod_family
AND id <> i_id
;
IF v_cnt = 0 THEN
RETURN(FALSE);
ELSE
RETURN(TRUE);
END IF;
EXCEPTION
WHEN OTHERS THEN
RETURN(FALSE);
END Prod_Family_Exists;
END Z_TEST;`
Validation Call in APEX - PL/SQL Function (Returning Boolean):
BEGIN
IF z_test.prod_family_exists( i_id => :id
,i_prod_family => :prod_family)
THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END;
Based on the image below and the code I've provided, my custom error message would only be raised if the result from the validation returned FALSE right? Is the code bad?

I figured this out. The code for the validation call to the support package function needed to be modified. New code:
BEGIN
IF z_test.prod_family_exists( i_id => NVL(:id,0)
,i_prod_family => :prod_family)
THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END;

Related

Check if a oracle view on a dblink is available?

I've got a lot of stored procedure working on a view on a database-link.
I would like to check if the view is available before to execute all the procedures so I've created a check() function in an object oriented programming way.
Now I can make: if(checked()=1) then ..work.. else null; end if;
The function is:
create or replace FUNCTION CHECK_MYVIEW RETURN NUMBER IS
CHECKED NUMBER;
BEGIN
BEGIN
select 1
into CHECKED
from MYVIEW
where rownum = 1
;
EXCEPTION WHEN OTHERS THEN
CHECKED:=0;
END;
RETURN CHECKED;
END CHECK_MYVIEW;
I've written the check query after some test and the result is fine.
With this kind of select I can tell if the view have some record and even if there is some connection problem with the remote database. But this is my own solution.
Is there an optimized oracle query to obtain the same feature?
For any kind of exception don't work without the raise of an exception...
You could try to check metadata:
DECLARE
checked INT;
BEGIN
SELECT COUNT(*)
INTO checked
FROM all_views#dblink
WHERE name = 'xxx'
AND owner = 'yyy';
--checked 0 -- not exists, 1 - exists
END;

PL/SQL Dynamic action [Set Value] not recognizing apex item value

I have a problem setting the value of an apex item (P13_3) using a pl/sql defined dynamic action. At the moment the dynamic action is triggered using a button. For example if "530000000019" is entered into the item (P13_3), after the clicking the button it should return a value (Product Code) and set the item with that value.
This is the pl/sql code that runs when the button is clicked:
DECLARE
p_code products.prod_code%TYPE;
p_id products.prod_id%TYPE;
BEGIN
p_id := :P13_2;
SELECT prod_code INTO p_code FROM products WHERE prod_id = p_id;
RETURN p_code;
END;
This is the error that appears:
Ajax call returned server error ORA-01403: no data found for Set Value.
This means that no data was returned when the SELECT INTO clause ran. I then altered the code and ran this code to see if there were any faults in the code:
DECLARE
p_code products.prod_code%TYPE;
p_id products.prod_id%TYPE;
BEGIN
p_id := 530000000019;
SELECT prod_code INTO p_code FROM products WHERE prod_id = p_id;
RETURN p_code;
END;
This code returned a value and the set value dynamic action was successful. This therefore means, that in apex was not picking up the value in the P13_3 item.
I have a apex process that has similar syntax that calls the P13_3 apex item and it runs successfully. Here is the code of the apex process:
DECLARE
b_code products.prod_id%TYPE := :P13_3;
p_quant products.prod_qnty%TYPE := :P13_2;
BEGIN
UPDATE products
SET prod_qnty = prod_qnty - p_quant
WHERE prod_id = b_code;
END;
If I am not mistaken, I would say that this proves that the problem lies with dynamic action and not the pl/sql code. I am currently using apex 5.1 (The 16 Dec release). Please Help. Thank you in advance :)
I have tried similar work ,I have created two text boxes one for input & another for output & I am getting expected result .Please check following screen shots of result & dynamic action settings
And dynamic action settings is as follows :
Edit view of True action is as follows :
Hope this will help you.

User-Defined Types (PL/SQL) in Oracle Forms

I'm using PL/SQL, and I've recently found out that you can do OOP with it.
The thing is that I've created an Object Type on database level with, like this:
CREATE OR REPLACE TYPE customer AS OBJECT
(
customer_id NUMBER(10)
,customer_name VARCHAR2(30)
,customer_last VARCHAR2(30)
,constructor function customer(p_id NUMBER)
RETURN SELF AS RESULT
,member procedure display
)
And the type's body:
CREATE OR REPLACE TYPE BODY customer AS
constructor function customer(p_id NUMBER)
RETURN SELF AS RESULT
AS
BEGIN
SELECT client_id,
client_name,
client_last
INTO self.customer_id,
self.customer_name,
self.customer_last
FROM clients
WHERE client_id = p_id;
RETURN;
END;
member procedure display IS
BEGIN
dbms_output.put_line('Name: ' || customer_name||' '|| customer_last);
END;
END;
This compiles great and has no problems.
The thing is when I try to use this new type on Oracle Forms.
I have a very simple form, with just a textbox to put the client_id, and a button to search for the customer (it uses the constructor function from the customer type to create a new instance and return it), and another textbox where it displays the customer name. When pressed it executes a Program Unit that goes like this:
PROCEDURE search_client IS
client customer;
BEGIN
IF :CLIENT.ID_CLIENT IS NOT NULL THEN
client := customer(p_id => :CLIENT.ID_CLIENT);
:CLIENT.NAME := client.customer_name;
END IF;
END;
When I try to compile it, I'm getting this errors:
Error 0 in Line 3, column 11
Item ignored
and a few more errors like that.
I've read somewhere that client side PLSQL does not support this kind of defined types. Is that true? Or there's any other mistakes that I'm no seen here.
By the way I'm using Oracle Forms 11g, and an Oracle 10g Data base.

Oracle raise_application_error error number best practice

I have a question regarding the error codes (-20000 to -20999) for Raise Application Error.
Can we use same error code (eg -20000) only for different error scenarios at multiple places in PLSQL code?
If we can use same error code in all places, why do we have 1000 codes?
What is the best practice to use error codes in Raise Application Error?
Sample Code:
create table t(id number primary key);
declare
begin
insert into t(id) values (1);
insert into t(id) values (1);
commit;
exception
when dup_val_on_index
then
raise_application_error(-20000, 'Cannot Insert duplicates');
when others
then
raise_application_error(-20000, sqlcode||'-'||sqlerrm);
end;
Can we use same error code (eg -20000) only for different error scenarios at multiple places in PLSQL code?
As Justin notes, you can certainly do that - just use one code. But it is likely to lead to confusion. I've seen it done, and usually in that case, the developers simply embed all critical information into the message, even including a code (they might, for example, already be using their own error codes that fall outside the acceptable range).
I suggest you follow Oracle's lead: assign ranges to areas of your application and then use error codes within a range when an application-specific error occurs in that part of the part.
If we can use same error code in all places, why do we have 1000 codes?
See above.
What is the best practice to use error codes in Raise Application Error?
Create a table in which you "register" error codes that are used, along with the message. Then developers can check to see "their" error is already registered and can re-use it. Or, more likely, they register a new error code and message.
Either way, you have a central point from which to organize the codes and hopefully minimize the change of two developers using the same error code.
Here's a script that does what I suggested above, along with a utility to generate a package with all of the errors defined and available for "soft-coded" reference.
CREATE TABLE msg_info (
msgcode INTEGER,
msgtype VARCHAR2(30),
msgtext VARCHAR2(2000),
msgname VARCHAR2(30),
description VARCHAR2(2000)
);
CREATE OR REPLACE PACKAGE msginfo
IS
FUNCTION text (
code_in IN INTEGER
, type_in IN VARCHAR2
, use_sqlerrm IN BOOLEAN := TRUE
)
RETURN VARCHAR2;
FUNCTION name (code_in IN INTEGER, type_in IN VARCHAR2)
RETURN VARCHAR2;
PROCEDURE genpkg (
NAME_IN IN VARCHAR2
, oradev_use IN BOOLEAN := FALSE
, to_file_in IN BOOLEAN := TRUE
, dir_in IN VARCHAR2 := 'DEMO' -- UTL_FILE directory
, ext_in IN VARCHAR2 := 'pkg'
);
END;
/
CREATE OR REPLACE PACKAGE BODY msginfo
IS
FUNCTION msgrow (code_in IN INTEGER, type_in IN VARCHAR2)
RETURN msg_info%ROWTYPE
IS
CURSOR msg_cur
IS
SELECT *
FROM msg_info
WHERE msgtype = type_in AND msgcode = code_in;
msg_rec msg_info%ROWTYPE;
BEGIN
OPEN msg_cur;
FETCH msg_cur INTO msg_rec;
CLOSE msg_cur;
RETURN msg_rec;
END;
FUNCTION text (
code_in IN INTEGER
, type_in IN VARCHAR2
, use_sqlerrm IN BOOLEAN := TRUE
)
RETURN VARCHAR2
IS
msg_rec msg_info%ROWTYPE := msgrow (code_in, type_in);
BEGIN
IF msg_rec.msgtext IS NULL AND use_sqlerrm
THEN
msg_rec.msgtext := SQLERRM (code_in);
END IF;
RETURN msg_rec.msgtext;
END;
FUNCTION NAME (code_in IN INTEGER, type_in IN VARCHAR2)
RETURN VARCHAR2
IS
msg_rec msg_info%ROWTYPE := msgrow (code_in, type_in);
BEGIN
RETURN msg_rec.msgname;
END;
PROCEDURE genpkg (
NAME_IN IN VARCHAR2
, oradev_use IN BOOLEAN := FALSE
, to_file_in IN BOOLEAN := TRUE
, dir_in IN VARCHAR2 := 'DEMO'
, ext_in IN VARCHAR2 := 'pkg'
)
IS
CURSOR exc_20000
IS
SELECT *
FROM msg_info
WHERE msgcode BETWEEN -20999 AND -20000 AND msgtype = 'EXCEPTION';
-- Send output to file or screen?
v_to_screen BOOLEAN := NVL (NOT to_file_in, TRUE);
v_file VARCHAR2 (1000) := name_in || '.' || ext_in;
-- Array of output for package
TYPE lines_t IS TABLE OF VARCHAR2 (1000)
INDEX BY BINARY_INTEGER;
output lines_t;
-- Now pl simply writes to the array.
PROCEDURE pl (str IN VARCHAR2)
IS
BEGIN
output (NVL (output.LAST, 0) + 1) := str;
END;
-- Dump to screen or file.
PROCEDURE dump_output
IS
BEGIN
IF v_to_screen
THEN
FOR indx IN output.FIRST .. output.LAST
LOOP
DBMS_OUTPUT.put_line (output (indx));
END LOOP;
ELSE
-- Send output to the specified file.
DECLARE
fid UTL_FILE.file_type;
BEGIN
fid := UTL_FILE.fopen (dir_in, v_file, 'W');
FOR indx IN output.FIRST .. output.LAST
LOOP
UTL_FILE.put_line (fid, output (indx));
END LOOP;
UTL_FILE.fclose (fid);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ( 'Failure to write output to '
|| dir_in
|| '/'
|| v_file
);
UTL_FILE.fclose (fid);
END;
END IF;
END dump_output;
BEGIN
/* Simple generator, based on DBMS_OUTPUT. */
pl ('CREATE OR REPLACE PACKAGE ' || NAME_IN);
pl ('IS ');
FOR msg_rec IN exc_20000
LOOP
IF exc_20000%ROWCOUNT > 1
THEN
pl (' ');
END IF;
pl (' exc_' || msg_rec.msgname || ' EXCEPTION;');
pl ( ' en_'
|| msg_rec.msgname
|| ' CONSTANT INTEGER := '
|| msg_rec.msgcode
|| ';'
);
pl ( ' PRAGMA EXCEPTION_INIT (exc_'
|| msg_rec.msgname
|| ', '
|| msg_rec.msgcode
|| ');'
);
IF oradev_use
THEN
pl (' FUNCTION ' || msg_rec.msgname || ' RETURN INTEGER;');
END IF;
END LOOP;
pl ('END ' || NAME_IN || ';');
pl ('/');
IF oradev_use
THEN
pl ('CREATE OR REPLACE PACKAGE BODY ' || NAME_IN);
pl ('IS ');
FOR msg_rec IN exc_20000
LOOP
pl (' FUNCTION ' || msg_rec.msgname || ' RETURN INTEGER');
pl (' IS BEGIN RETURN en_' || msg_rec.msgname || '; END;');
pl (' ');
END LOOP;
pl ('END ' || NAME_IN || ';');
pl ('/');
END IF;
dump_output;
END;
END;
/
/* Sample data to be used in package generation. */
BEGIN
INSERT INTO msg_info
VALUES (-20100, 'EXCEPTION', 'Balance too low', 'bal_too_low'
, 'Description');
INSERT INTO msg_info
VALUES (-20200, 'EXCEPTION', 'Employee too young', 'emp_too_young'
, 'Description');
COMMIT;
END;
/
Can you use the same error code every time? Sure.
Should you? Almost certainly not. It would be rather annoying if Oracle raised an ORA-00001 error for every possible thing that went wrong-- a primary key violation, a foreign key violation, an unexpected internal error, a tablespace running out of space, a permission error, etc.-- because that makes it much more difficult for developers to handle the errors they can and propagate those they cannot. You'd have to do things like parse the text of the error string to figure out what went wrong and to figure out whether it's an error that you can handle or not. And heaven forbid that Oracle ever change the text of an error message or your parser causes you to misinterpret an error message. Similarly, it would generally be annoying if your code threw the same error code for every possible problem that was encountered.
If you are going to use a custom error code, it should communicate something above and beyond what the Oracle error code provides. It makes no sense, for example, to have a when others that converts the nice, usable Oracle error message and error stack to a pointless user-defined error. It may make perfect sense, though, for an ORA-20001 error to indicate that a foo already exists and an ORA-20002 error to indicate that a bar already exists when you have an application that deals with lots of foo's and bar's and those errors would make more sense to a user than a generic duplicate key error.
Just because you can do something doesn't mean that you should do something. Personally, as a consultant that comes in to mop up other people's messes, we (hired guns) hate developers that don't provide useful error messages in their code. Oracle does a pretty good job - most of the time. In the case of DUPS, it would be nice if in the error messaging, the developers added one more line to their error message and that would be the value being duplicated - what a concept. Maybe you don't send the text back to an end user, but should consider using dbms_system.ksdwrt - a proc that writes these errors to the alert log.
1) Can we use same error code (eg -20000) only for different error
scenarios at multiple places in PLSQL code?
You can. It is however not always useful - read on.
2) If we can use same error code in all places, why do we have 1000
codes?
To distinguish between different error conditions - read on.
3) What is the best practice to use error codes in Raise Application
Error?
Define some in-house standard, and keep following it - read on.
So, what is the point for user defined errors? They are great to show business logic problems (e.g. a newly added customer seems to be 950 years old, or born in the future), while standard Oracle errors show storage logic problems (e.g. referring to an unknown customer).
Depending on your application model, you may have the business logic implemented outside of the database, or you may prefer leveraging the powers of PL/SQL to implement the business logic inside the database.
This however is not too useful:
EXCEPTION
When Dup_val_on_index
then
Raise_application_error(-20000,'Cannot Insert duplicates');
WHEN OTHERS
THEN
Raise_application_error(-20000,SQLCODE||'-'||SQLERRM);
end;
First, you may ask yourself, what do you do in case of an error? Ok, you can print a nice call stack to the face of the end user - which is totally useless. I would say you can isolate all possible problems into something what the user can fix (i.e. pick a different login name if you hit a unique constraint), and errors what the user can not fix (for example, referring to a non-existing user because of a programming mistake).
I would say it makes sense to distinguish between errors this way. If you feel this logical, then:
wrap all exceptions to a custom BusinessError with e.g. -20000 if the user can do something with that. Use the error message to provide a useful description of what to do (i.e. "please pick a different login name" instead of "cannot insert duplicates") - and then your application is already in the top 5%.
wrap all other exceptions as a technical error, say with -20001. In case of an unexpected technical error, your application can do one thing: log it, and show a nice "something went wrong but we're trying to handle it" message to the user. Very less users can read/or interested to read Oracle call stacks :)
I am not totally sold on the standard oracle error messages. For example, a simple constraint violation error can be quite cryptic. I suggest to explicitly name all constraints, and follow a good naming convention. In that case, you don't need to actually handle any built-in Oracle error - as they will go to the application code, which will log them and show "something went wrong" to the user as it is not a business error.
Limitations:
This solution works very well if you have the business logic implemented in the database in PL/SQL. Of course, if you prefer application server business logic, e.g. Hibernate, then you'll have the fun to handle each and every constraint violation on your own.
This solution may need extensions if you're building a multi-language application. Business errors will have meaningful, human-friendly messages, and that comes in one language. Either you may need to throw a message in multiple languages (and pick the right one on the GUI), or implement your own translation file on top of it.
An example with business errors (one internal error, the dup_val_on_index is wrapped to business error, since that is caused by a wrong user input)
procedure addUser(in_birthDate date, ) is
pls_integer age=sysdate-in_birthDate;
begin
if age>100 then
raise_application_error(-20000,'Check birth date, you''re too old!');
elsif age<0 then
raise_application_error(-20000,'Birth date can not be in the future.');
end if;
insert into....
commit;
exception
when dup_val_on_index then
raise_application_error(-20000,'Please pick a different login name!');
end;
You may consider creating a procedure for throwing a business error:
CREATE OR REPLACE
Procedure throw (in_message varchar2) is
begin
raise_application_error(-20999,in_message);
end;
Which makes your code more readable:
begin
if age>100 then
throw('Check birth date, you''re too old!');

How can I revert changes to a record in Oracle Forms 6i?

I have a form running in Oracle Forms 6i which has tabular formatted rows that are being populated from a certain table in the database. One column has a [List_Of_Values] property enabled to allow the user to select among possible values.
Some values among the list can only be selected if the user has permission to do that, and I have created a [ WHEN-VALIDATE-ITEM ] trigger to check the permission after the value has been changed. The trigger raises a form_trigger_failure to prevent the user from saving the changes done.
The problem is that if the user gets notified about lack of permission to select the value, then there is no way for the user to know the previous (old) value to select it again, unless the form is cancelled which will cause his other (valid) changes to be lost too.
Here is the code I have written in the trigger
DECLARE
NEW_LOCATION VARCHAR2(100);
BEGIN
NEW_LOCATION := :BLK_MAT_STG_PLACES_PILE.STG_LOC_ID;
IF NEW_LOCATION LIKE 'OH01%' THEN
IF NOT :GLOBAL.USER_ID LIKE 'Admin%' THEN
MESSAGEBOX('You are not authorized to select this value');
/* What can I write to load the old value to this item? */
RAISE FORM_TRIGGER_FAILURE;
END IF;
END IF;
END;
I have tried ROLLBACK but that did not revert the old value to the form. I tried SYNCHRONIZE as well, but that had no effect. Is there any option other than going through the database table again to pull out the value?
BEGIN
IF NEW_LOCATION LIKE 'OH01%' THEN
IF NOT :GLOBAL.USER_ID LIKE 'Admin%' THEN
MESSAGEBOX('You are not authorized to select this value');
/* Return it to the original value that was fetched from database */
:BLK_MAT_STG_PLACES_PILE.STG_LOC_ID :=
get_item_property('BLK_MAT_STG_PLACES_PILE.STG_LOC_ID'
,DATABASE_VALUE);
RAISE FORM_TRIGGER_FAILURE;
END IF;
END IF;
END;
One of my colleagues found the solution to this problem as follows:
Define a Global Parameter in the parameter list (I named it TEMP_LOCATION)
In the PRE-TEXT-ITEM trigger (which is executed before navigating to the item) I wrote
BEGIN
:GLOBAL.TEMP_LOCATION := :BLK_MAT_STG_PLACES_PILE.STG_LOC_ID;
END;
3 Then in the WHEN-VALIDATE-ITEM trigger code that I wrote in this question, I cancelled raising FORM_TRIGGER_FAILURE and simply filled the item with the TEMP_LOCATION
DECLARE
NEW_LOC VARCHAR2(100);
BEGIN
NEW_LOC := :BLK_MAT_STG_PLACES_PILE.STG_LOC_ID;
IF NEW_LOC LIKE 'OH01%' THEN
IF NOT :GLOBAL.USER_ID LIKE 'Admin_%' THEN
MESSAGE('YOU ARE NOT AUTHORIZED TO SELECT THIS VALUE');
:BLK_MAT_STG_PLACES_PILE.STG_LOC_ID := :GLOBAL.TEMP_LOCATION;
/* this solved my problem */
END IF;
END IF;
END;
I thank all of those who tried to help. If someone comes up with a better answer than my own, then I will happily accept it.

Resources