Increment in Oracle Forms without commit record - plsql

I have used this code to generating an auto number:
DECLARE
ACC_NEW_ID NUMBER:=0;
BEGIN
if :acc_info_id is null then
SELECT MAX(NVL(ACC_INFO_ID,1000))+1 INTO ACC_NEW_ID FROM ACC_INFO;
:ACC_INFO_ID := ACC_NEW_ID;
end if;
END;
This code is working perfectly, but when I create one further record without clicking save button it creates the same number. For example: id is 1003 and after posting the record, I clicked new record button instead of save button, It generates the same 1003 number instead of 1004 as I expected.

Your code queries the records in the table, and returns the maximum ID that it can see at that point in time (i.e. it won't see uncommitted records), and adds one to it.
If it generates 1003 but no record with this value is saved into the table, of course you should expect it to not find it!
As far as that is concerned, it "works perfectly" and correctly. If, however, the intention is to generate a unique ID value for each record, this approach is flawed and will fail in any normal system due to concurrency.
Instead, if you need a unique ID you should use an Oracle SEQUENCE instead, which guarantees uniqueness.

Related

Oracle APEX PL/SQL process to insert multi-select items into association table for m:m relationship failing silently

I am implementing a form on table that allows the end-user to create a new project. This form contains a shuttle that allows the user to select the disposal site(s)(1+) that the project pertains to. I would like to use the output of the shuttle values to populate an association table between projects and disposal sites which is a many to many relationship.
This is my approach so far:
Created an additional VARCHAR2(4000)in the projects table to store the shuttle output (called 'Shuttle'). The shuttle output in this column looks something like 'CA-AT-D109Z2:CA-AT-D115:CA-AT-D174Z2'.
Created a process to take separate based on ':' and then add the values to the association table using the PL/SQL code:
Declare
Cursor c_values
is
Select
t.column_value As disposal_sites
From
Table ( apex_string.split(:P28_SHUTTLE, ':') ) t
Where
t.column_value Is Not Null;
Begin
for c in c_values loop
insert into MP_MDB_PROJECT_2_DSITE (PROJECTIDFK,DISPOSALSITEIDFK)
values (:P28_PROJECTNUMBER,c.disposal_sites);
end loop;
End;
The process/code enters the values from the shuttle into the association table in a loop as expected for the the disposal site but remains blank for projectidfk (the key that is '1' in the 1:m relationship). The code doesn't throw an error so I am having trouble debugging.
I think perhaps the problem I am having is that project number is computed after submission based on the users selections.Therefore, when the process run it finds :P28_PROJECTNUMBER to be null. Is there a way to ensure the computation to determine :P28_PROJECTNUMBER takes places first and is then followed by the PL/SQL process?
All help appreciated
If the form you're implementing is a native apex form, then you can use the attribute "Return Primary Key(s) after Insert" in the "Automatic Row Processing - DML" process to ensure that the primary key page item contains inserted value for any processes execute after this process.
Just make sure the process that handles the shuttle data is executed after the DML process.

age control 2 for pl/sql

i am trying to find out at what age an employee started working.
if he started under 16 he should report this 'Error when entering the date of birth' mistake. so my trigger is created but my trigger is not working
I get ever this error: ORA-01422: Exact retrieval returns more than the requested number of lines
i can't find the problem
Here is the code:
SET SERVEROUTPUT ON
ACCEPT Birthday PROMPT ' Pleas give you Date of birth: '
CREATE OR REPLACE TRIGGER T_Controll
before INSERT ON meine_Firma -- Table
FOR EACH ROW
DECLARE
V_Berufstart meine_Firma.Hiredate%TYPE; --Job begin
V_Geburtsdatum DATE; -- Date of birth
V_Alter number:=0; -- AGE
SELECT HIREDATE INTO V_Berufstart FROM meine_Firma;
BEGIN
V_Geburtsdatum:=('&Birthday');
V_Alter:= Round(MONTHS_BETWEEN(V_Berufstart,V_Geburtsdatum)-2)/12;
IF 16 > V_Alter THEN
RAISE_APPLICATION_ERROR(-20201,'Error when entering the date of birth');
END IF;
END;
/
SET SERVEROUTPUT OFF
If he under 16 then he may not work
sorry my english is not good (=
You have a much bigger issue in this script than the error you are getting. Even after correcting as #ShaunPeterson suggested it will still fail
, it WILL NOT generate an error it will just not run as you expect. The issue is you failed to understand substitution variables - the use of &name (Specifically here &Birthday.) I'll actually use &Birthday in the following but the discussion applies to ANY/ALL substitution variables.
people fail to understand why they can't use the "&" substitution
variables in their PL/SQL procedures and functions to prompt for input
at run time. This article will hopefully help clarify in your mind
what the differences are so that you can understand where and when to
use these.
Substitution Variables The clue here is in the name... "substitution". It relates to values being substituted into the code
before it is submitted to the database. These substitutions are
carried out by the interface being used
The effect of this substitution is the the line containing the substitution variable is physically rewritten by the interface replacing %Birthday. In this case if you don't enter a value or the date 2000-05-19 the statement before and after substitution is
BEFORE: V_Geburtsdatum:=('&Birthday');
AFTER: V_Geburtsdatum:=(''); OR V_Geburtsdatum:=('2000-05-19');
Either way the after is what the compiler sees; it does NOT see %Birthday at all. Moreover when run the trigger will not prompt for a value. As far as the compiler is concerned it is a hard coded value that will never change. Beyond that a trigger, or any other PLSQL script (stored or anonymous) never prompts for values, they are actually incapable of doing so as it is not part of the language. Any prompt is via your interface software not plsql.
I'm going to suggest a way to avoid the trigger altogether. Getting on soap box: Triggers are BAD, they have some usefulness for assigning auto incrementing keys (before 12c),logging, very limited auditing, etc. However, for business rules they should be the option of last resort. Ok Get off soap box.
The first thing is to make the columns meine_Firma.Hiredate and meine_Firma.Geburtsdatum NOT null (if not already). If either are NULL you cannot calculate anything with them, the result would be NULL.
Second create a new column age_at_hire (or whatever) as a virtual column then put a check constraint on it. And voila trigger no longer needed. See fiddle for demo.
So the proposed change (YES you will probably have to clean up the bad data first):
alter table meine_Firma modify
( hiredate not null
, Geburtsdatum not null
) ;
alter table meine_Firma add
( age_at_hire integer generated always as (trunc(months_between(hiredate,Geburtsdatum))) virtual
, constraint check_age_at_hire check (age_at_hire >= 16*12)
);
Anyway, I hope you get an understanding of substitution variables for the future. And learn to avoid triggers. Good luck.
The reason you are getting that specific error is that the below select will select ALL rows from meine_Firma as there is no where clause
SELECT HIREDATE INTO V_Berufstart FROM meine_Firma;
However because you are in a trigger you do not need to select anything you use the :NEW bind variable. So you can just use
V_Berufstart := :NEW.HIREDATE;
If this was an update trigger there would be both a :NEW and :OLD bind variable declared so that you can access the OLD and NEW values. As this is an Insert trigger the :OLD will just be null as there is no old values.

How to assign an ID but then delete if not used

I am unsure on how to do this 'best practice' wise.
I have a web application (asp.net VB) that connects to an MS SQL server 2012. Currently when the page loads the app connects to a DB table and gets the last ID and adds 1 to it and displays this to the user. When the user submits the form the new ID is saved to the DB.
The problem being the app may be opened by 2 users at the same time and therefore they will get assigned the same ref number which will cause problems when the data is saved.
How can I assign different numbers to different users if the app is opened at the same time without saving unnecessary data?
You have multiple solutions for this, I'll try to outline a few approaches. (I'll assume that you need to insert things into a DB that I'll call "orders".)
First of all, you can move the ID-generation to the moment when the order is actually inserted, and not at the moment when the user start to enter the data. That way, you do not generate an ID for a user that never completes the form. Also this scenario is easy to accomplish using autoincrementing values in sql server. You can, for example do:
-- create a table with an identity column
create table Orders (
ID int identity(1,1) not null,
Description nvarchar(max) not null
);
-- insert values, without specifying the ID column
insert into Orders (Description) values ()
-- select the row back
-- returns 1, 'My First Order'
select * from Orders;
Another way to do this is to use SQL Server Sequences. These are things that do nothing except generate numbers in a row. They guarantee that the numbers won't be repeated, and always keep count of the current value, i.e.
-- create a sequence
create sequence OrderIdSequence
start with 1
increment by 1;
-- get the next sequence value
select next value for OrderIdSequence

Order of execution of trigger and statements in Oracle stored procedure

Below are my table structures :
Table -Customer
CustomerID Blacklisted Customer Name
101 Y ABC
102 Y DEF
Table -Blacklist
CustomerID BlacklistID Customer Name
101 1011 ABC
102 1012 DEF
Table -Reason
BlacklistID ReasonID Reason Code
1012 02 Rcode2
Main table "Customer" is to store customer information.There is a trigger after update on table "Customer" to insert record in table "Blacklist" if somebody updates the blacklisted as Y in customer table.
We consider the customer as blacklisted if ,
Blacklisted column in Customer table as value 'Y' and.
There are records present for customer in Blacklist and Reason table
Now my requirement is to blacklist the customer from backend.For this i am writing stored procedure with below queries:
Update customer set blacklisted ='Y' where customerid='102';
select BlacklistID into var_id from blacklist where customerid='102';
Insert into reason(BlacklistID,ReasonID,ReasonCode)values(var_ id,111,'RCODE1');
Now to insert entry in Reason table(step-3),i need BlacklistID which is a foreign key and i will get the value of BlacklistID once the trigger on customer table gets exceuted.So my confusion is, can i assume the trigger on update of 'Customer' table will always get excuted before the cntrl reaches my INSERT INTO reason(step-3) statement. Please suggest.
If you need to be certain about the order of trigger execution, you can specify this order when creating the trigger.
This is done with the FOLLOWS ... and PRECEEDS ... options of the create trigger statement:
More details in the manual: http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/create_trigger.htm#CJAEJAFB
FOLLOWS | PRECEDES
Specifies the relative firing of triggers that have the same timing point. It is especially useful when creating crossedition triggers, which must fire in a specific order to achieve their purpose.
Use FOLLOWS to indicate that the trigger being created must fire after the specified triggers. You can specify FOLLOWS for a conventional trigger or for a forward crossedition trigger.
Use PRECEDES to indicate that the trigger being created must fire before the specified triggers. You can specify PRECEDES only for a reverse crossedition trigger.
Yes. Triggers are part of the statement. Although you cannot be fully certain *) of the order in which multiple triggers in the same statement are executed, you can be certain that they al are done when the statement itself is done. So by the time of step 2, all update triggers of step one have fired.
*) Actually, the default order is:
Statement level before triggers
Row level before triggers
Row level after triggers
Statement level after triggers
But if you have, say, two row level before trigger, by default you cannot be certain in which order those two are executed. But I learned from the comments that in Oracle 11, you can actually specify the order to cover even those cases.
I don't see a need for all these tables, and therefore no need for a trigger.
Why do you not just use a blacklist reason code in the customer table? The purpose of the blacklist table is unclear as it seems to add no data and just repeats data from the customer table with an additional id column?
Or if you need multiple reason codes then just use a blacklist_reason table that references the customer id and a reason code -- I don't think you even need the blacklisted column in the customer table.

Web Form Next Number Generation Scenario

I have a web form with several fields first field is Employee Number which is having "EMP - 0001" Format.i'm generating next Employee number by considering the last Emp Number added and converting the latter part to integer and add one
ex: split EMP - 0001 -> get 0001 -> convert it to integer -> add one -> generate next no as EMP - 0002
Next Employee Number should be visible to the user.my issue is when there are multiple users using the system.imagine that one user opens the web Form and doesn't save the record.his Employee Number is EMP - 0002.another user opens the web form he also sees the EMP no as 0002 because last record is not saved yet.2nd user saves the records he gets the Number 0002.1st user then saves the record.so at last i've got duplicate EMP Numbers in my database.what kind of scenario should i follow to over come this situation
The only way to accurately predict their ID is to put in a blank record, get the ID used, then when they enter the form, update the record with their information. However, if they quit the form, you're left with a blank record.
Insert the record on the DB and get the ID it returns. You won't have concurrency issues there if you're opening and disclosing your connection correctly.
Your current approach is prone to concurrency issues as you pointed out & I will not recommend it you. You have the following options.
Use an Identity column in your database table as a serial column (The database automatically increments the identity column on every insert row operation- you don't have to specify it through code)SQL Server Identity
Use a database sequence (Depeding on your database version & its support - A database sequence returns a unique integer value - this can also be cached, presented on form once the form loads - once generated, same sequence is never generated again)
Use a database trigger to automatically update the Id column on every row insert
Depeding on your requirement, you can pick one option.
After saving the record you can give the user message tah record is saved and your Employee numnber is "EMP - 0002".

Resources