Assign a unique value to a user each time - oracle

How to generate and assign a unique value (sequence) to a user every time a user logs in? If two users login in at the same time simultaneously, I should be able to avoid assigning same value to both.
And it is not a specific requirement. It can be any operation for which a sequence is required to be generated and assigned. I am trying to understand how the situation can be handled if the operation happens at the same time. I require a solution for Oracle 11g specifically.

You named it - use a sequence.
create sequence myseq;
Fetch value from it using
select myseq.nextval from dual;
Assingn it to that user. Though, I'm not sure I understand what that actually is; what would you assign it to, really? I hope you know.

Without understanding what exactly what you want to accomplish with the unique value, as you are not explaining it, you might do something like this:
Create a Sequence for creating an incremental sequential number
SQL> create sequence my_seq start with xxx increment by yyy cache zzz;
Where
xxx is the number where the sequence should start
yyy is the increment value of the sequence ( normally 1 )
As you plan to use it each time the user logs in, it might be a good idea to cache some values for faster access. zzz represents the number of values of the sequence you want to cache.
Assign the values to each user which logs in
A way to assign this value to a user each time logs in is through a logon trigger and related it to the properties of the session, using the advantages provided by the default context SYS_CONTEXT. Of course, for that you need a DBA user to install the trigger in an admin schema ( as sys or system )
An example to store those values in a custom audit table:
SQL> CREATE TRIGGER AFT_LOG_DBT
AFTER LOGON ON DATABASE
DECLARE
-- declare variables
v_db_user varchar2(100);
v_os_user varchar2(100);
v_seq number;
BEGIN
SELECT sys_context('userenv','session_id') , -- the session id
sys_context('userenv','osuser') , -- the os user
my_seq.nextval -- the sequence next val
INTO
v_db_user,
v_os_user,
v_seq
FROM DUAL;
-- insert those values into an audit table
insert into your_schema.log_audit ( logon_time , db_user, os_user, seq_val )
values ( sysdate , v_db_user, v_os_user, v_seq );
commit;
END;
As you were not explaining what you wanted to do with the unique sequence value, I tried to provide you with an example of a use case.
I have never gotten duplicate sequence numbers from a sequence generator unless it has wrapped the max value or is set to cycle.

Related

Which strategy would be better related to the use of NEXTVAL and CURRVAL in Oracle?

I am developing a Online Registration System using JSP and Oracle where I need to give every successful registrant a unique registration number sequentially. For this I want to adopt the NEXTVAL facility of Oracle, but I am in a dilemma for which policy I would adopt.
Policy 1: First I will store the NEXTVAL of a sequence in the following way.
select seq_form.nextval slno from dual;
Then I will store the slno into a variable say
int slno;
Now I will use the slno for in the insert query when the user finally submits the form, like
insert into members(registration_no, name,...) values(slno, name, ...);
Here the registration_no is primary key.
Policy 2: In my second policy, I will run the insert the query first
insert into members(registration_no, name,...) values(seq_form.nextval, name, ...);
fetch the last inserted ID like
select seq_form.currval slno from dual;
And then store the same in some variable say
int slno;
And use the same to show it to the registrant. Now I can't come to a conclusion which is better in terms of safety and efficiency. Here, I must make it clear that, in both the cases, my intension is to give the user a unique sequential number after successful submission of the form and by safety I meant to say that the user should get the ID respect to his/her own web session. Please help me.
I suggest you do it slightly differently:
Create a BEFORE INSERT trigger on your MEMBERS table. Set REGISTRATION_NO column to SEQ_FORM.NEXTVAL in the trigger:
CREATE OR REPLACE TRIGGER MEMBERS_BI
BEFORE INSERT ON MEMBERS
FOR EACH ROW
BEGIN
:NEW.REGISTRATION_NO := SEQ_FORM.NEXTVAL;
END MEMBERS_BI;
Do NOT put REGISTRATION_NO into the column list in your INSERT statement - it will be set by the trigger so there's no need to supply any value for it:
Use the RETURNING clause as part of the INSERT statement to get back the value put into REGISTRATION_NO by the trigger:
INSERT INTO MEMBERS (NAME, ...)
VALUES ('Fred', ...)
RETURNING REGISTRATION_NO INTO some_parameter
If you are using oracle 12, you can use identity column.
Then use RETURNING to get auto-generated value back.
Go with the policy 2. Because you cant always be sure that the insert query will be successful. If the insert fails, your oracle sequence has been rolled forward and you lose a sequence.
it is a better idea to insert and then later fetch it into a variable.

PL/SQL Procedure - Ticket system

I'm trying to make a Procedure in PL/SQL for a ticket system for my website.
I want a simple procedure that saves some data into the database.
It will have name of the user (random VARCHAR2) and a number and the date of today as input.
When this procedure is called it will automatically higher a number in the database (Maybe primary key) and add the input into the database. But I have no idea how to make something like this with the option to have more people access it at once.
Can anyone help please?
For the people that want to know what I have already.
CREATE OR REPLACE PROCEDURE VoegBoeteToe
(v_speler IN number, v_date IN date, v_boete IN number)
IS VoegBoeteToe
DECLARE
v_nextNum NUMBER;
BEGIN
SELECT BETALINGSNR
INTO v_nextNum
FROM BOETES
ORDER BY BETALINGSNR DESC;
v_nextNum := v_nextNum + 1;
INSERT INTO BOETES VALUES(v_nextNum,v_speler,v_date,v_boete);
end;
/
The fourth line of your procedure should be IS instead of IS VoegBoeteToe. In addition, remove the fifth line (DECLARE), which is not needed in a procedure. And use a sequence to derive the sequential number safely.
So, when all's said and done, your procedure should look something like:
CREATE SEQUENCE BOETES_SEQ;
CREATE OR REPLACE PROCEDURE VoegBoeteToe
(v_speler IN number, v_date IN date, v_boete IN number)
IS
BEGIN
INSERT INTO BOETES VALUES(BOETES_SEQ.NEXTVAL,v_speler,v_date,v_boete);
end;
It would also be a very good idea to include the list of field names in the BOETES table into which you're inserting values. This will make life better for anyone who has to maintain your code, and may save you from making a few errors.
Share and enjoy.
You need to create SEQUENCE
CREATE SEQUENCE DUMMY_SEQ
/
Then in Your code You can use something like that:
select DUMMY_SEQ.NEXTVAL into myVar from dual;
First, create a sequence. A sequence generates an ever incrementing number whenever it is invoked. See this for some nice examples. The documentation could be useful too.
CREATE SEQUENCE user_sequence
START WITH 1000 -- your sequence will start with 1000
INCREMENT BY 1; -- each subsequent number will increment by 1
Then,
SELECT user_sequence.NEXTVAL INTO v_nextNum FROM DUAL;

Get oracle sequenve next value without change it

I have a big problem. I need to know the next value of an oracle sequence without changing it.
If I use sequence.NEXTVAL , I get what I need. The problem is that the value changed, so the next record will get this value + 1 and it's not good for me.
I know I can use sequence.CURRVAL and it's great because it does not change the value, but in case of no records, it's not working and in this case the sequence value can be any number (and not only 1 cause the sequence value steel exist).
Please help me to find a solution.
Thank you vary much !!!!!
You should definitely not rely on never missing a value in a sequence, as they optimise for concurrency over sequential numbering. There are quite a few situations in which a number can be "lost".
Furthermore, the value visible in the dba_sequences may not be the actual next value, as the numbers are assigned from an in-memory cache. The underlying sequence metadata table has no data on the usage of that cache. You should also bear in mind that in a RAC system each instance has its own cache of sequence numbers.
You might describe the problem you are trying to solve, as it could be that sequences are not an appropriate mechanism for you.
In multi-user environment there is no way. The value is either known - when you call .NEXTVAL or unknown. Sequences can not be locked.
Well the only suggestion that I can give you is to get the nextval and then revert back. But CAUTION: Perform these operations only while you sequence is not begin used from any other session. Otherwise if sequence is fetched from another session during this operation, it is possible that you could never revert back to the original value. So be cautious before checking:
In my database, current value of seq_test is 6
--Get the next value
SQL> SELECT seq_test.NEXTVAL FROM dual;
NEXTVAL
----------
7
-- Ok, so this is the next value, now Restore to Original
SQL> ALTER SEQUENCE seq_test INCREMENT BY -1;
Sequence altered.
SQL> SELECT seq_test.NEXTVAL FROM dual;
NEXTVAL
----------
6
-- Restore original increment by clause
SQL> ALTER SEQUENCE seq_test INCREMENT BY 1;
Sequence altered.
--Check
SQL> SELECT seq_test.CURRVAL FROM dual;
CURRVAL
----------
6
I again warn you that this could be fatal ;-)
I can't see any reason why you want to know the next value of a sequence. The only importance for the value is when you have used it for a new record. So you either create a new record and return the ID value immediately:
declare
l_id number;
begin
Insert into table (id)
values (sequence.nextval)
return id into l_id;
-- and then do something more with the ID value
end;
or first get a new ID and use it afterwards
declare
l_id number;
begin
select sequence.nextval
into l_id
from dual;
-- then do something with the ID value
end;
Look at the "last_number" column from
select * from USER_SEQUENCES
Edit:
This works only if the sequence is created with the nocache option.
So if the sequence does has a cache setting, drop it and recreate it with nocache, and then you can use user_sequences.last_number +1.
Note that if the sequence is heavily used than this does impact performance. If on the other hand, the sequence is not used so much, it will be fine.
Short of writing the application (which is the best solution), this is only solution.

How to guarantee order of primary key and timestamp in oracle database

I am creating some record which have id, ts ... So firstly I call select to get ts and id:
select SEQ_table.nextval, CURRENT_TIMESTAMP from dual
and then I call insert
insert into table ...id, ts ...
this works good in 99 % but sometimes when there is a big load the order of record is bad because I need record.id < (record+1).id and record.ts < (record+1).ts but this conditional is met. How I can solve this problem ? I am using oracle database.
You should not use the result of a sequence for ordering. This might look strange but think about how sequences are cached and think about RAC. Every instance has it's own sequence cache .... For performance you need big caches. sequences had better be called random unique key generators that happen to work sequenctially most of the time.
The timestamp format has a time resolution upto microsecond level. When hardware becomes quicker and load increases it could be that you get multiple rows at the same time. There is not much you can do about that, until oracle takes the resolution a step farther again.
Use an INSERT trigger to populate the id and ts columns.
create table sotest
(
id number,
ts timestamp
);
create sequence soseq;
CREATE OR REPLACE TRIGGER SOTEST_BI_TRIG BEFORE
INSERT ON SOTEST REFERENCING NEW AS NEW FOR EACH ROW
BEGIN
:new.id := soseq.nextval;
:new.ts := CURRENT_TIMESTAMP;
END;
/
PHIL#PHILL11G2 > insert into sotest values (NULL,NULL);
1 row created.
PHIL#PHILL11G2 > select * from sotest;
ID TS
---------- ----------------------------------
1 11-MAY-12 13.29.33.771515
PHIL#PHILL11G2 >
You should also pay attention to the other answer provided. Is id meant to be a meaningless primary key (it usually is in apps - it's just a key to join on)?

Incrementing Oracle Sequence by certain amount

I am programming a Windows Application (in Qt 4.6) which - at some point - inserts any number of datasets between 1 and around 76000 into some oracle (10.2) table. The application has to retrieve the primary keys, or at least the primary key range, from a sequence. It will then store the IDs in a list which is used for Batch Execution of a prepared query.
(Note: Triggers shall not be used, and the sequence is used by other tasks as well)
In order to avoid calling the sequence X times, I would like to increment the sequence by X instead.
What I have found out so far, is that the following code would be possible in a procedure:
ALTER SEQUENCE my_sequence INCREMENT BY X;
SELECT my_sequence.CURVAL + 1, my_sequence.NEXTVAL
INTO v_first_number, v_last_number
FROM dual;
ALTER SEQUENCE my_sequence INCREMENT BY 1;
I have two major concerns though:
I have read that ALTER SEQUENCE produces an implicit commit. Does this mean the transaction started by the Windows Application will be commited? If so, can you somehow avoid it?
Is this concept multi-user proof? Or could the following thing happen:
Sequence is at 10,000
Session A sets increment to 2,000
Session A selects 10,001 as first and 12,000 as last
Session B sets increment to 5,000
Session A sets increment to 1
Session B selects 12,001 as first and 12,001 as last
Session B sets increment to 1
Even if the procedure would be rather quick, it is not that unlikely in my application that two different users cause the procedure to be called almost simultaneously
1) ALTER SEQUENCE is DDL so it implicitly commits before and after the statement. The database transaction started by the Windows application will be committed. If you are using a distributed transaction coordinator other than the Oracle database, hopefully the transaction coordinator will commit the entire distributed transaction but transaction coordinators will sometimes have problems with commits issued that it is not aware of.
There is nothing that you can do to prevent DDL from committing.
2) The scenario you outline with multiple users is quite possible. So it doesn't sound like this approach would behave correctly in your environment.
You could potentially use the DBMS_LOCK package to ensure that only one session is calling your procedure at any point in time and then call the sequence N times from a single SQL statement. But if other processes are also using the sequence, there is no guarantee that you'll get a contiguous set of values.
CREATE PROCEDURE some_proc( p_num_rows IN NUMBER,
p_first_val OUT NUMBER,
p_last_val OUT NUMBER )
AS
l_lockhandle VARCHAR2(128);
l_lock_return_code INTEGER;
BEGIN
dbms_lock.allocate_unique( 'SOME_PROC_LOCK',
l_lockhandle );
l_lock_return_code := dbms_lock.request( lockhandle => l_lockhandle,
lockmode => dbms_lock.x_mode,
release_on_commit => true );
if( l_lock_return_code IN (0, 4) ) -- Success or already owned
then
<<do something>>
end if;
dbms_lock.release( l_lockhandle );
END;
Altering the sequence in this scenario is really bad idea. Particularly in multiuser environment. You'll get your transaction committed and probably several "race condition" data bugs or integrity errors.
It would be appropriate if you had legacy data alredy imported and want to insert new data with ids from sequence. Then you may alter the sequence to move currval to max existing ...
It seems to me that here you want to generate Ids from the sequence. That need not to be done by
select seq.nextval into l_variable from dual;
insert into table (id, ...) values (l_variable, ....);
You can use the sequence directly in the insert:
insert into table values (id, ...) values (seq.nextval, ....);
and optionally get the assigned value back by
insert into table values (id, ...) values (seq.nextval, ....)
returning id into l_variable;
It certainly is possible even for bulk operations with execBatch. Either just creating the ids or even returning them. I am not sure about the right syntax in java but it will be something about the lines
insert into table values (id, ...) values (seq.nextval, ....)
returning id bulk collect into l_cursor;
and you'll be given a ResultSet to browse the assigned numbers.
You can't prevent the implicit commit.
Your solution is not multi user proof. It is perfectly possible that another session will have 'restored' the increment to 1, just as you described.
I would suggest you keep fetching values one by one from the sequence, store these IDs one by one on your list and have the batch execution operate on that list.
What is the reason that you want to fetch a contiguous block of values from the sequence? I would not be too worried about performance, but maybe there are other requirements that I don't know of.
In Oracle, you can use following query to get next N values from a sequence that increments by one:
select level, PDQ_ACT_COMB_SEQ.nextval as seq from dual connect by level <= 5;

Resources