Best way to reset an Oracle sequence to the next value in an existing column? - sql

For some reason, people in the past have inserted data without using sequence.NEXTVAL. So when I go to use sequence.NEXTVAL in order to populate a table, I get a PK violation, since that number is already in use in the table.
How can I update the next value so that it is usable? Right now, I'm just inserting over and over until it's successful (INSERT INTO tbl (pk) VALUES (sequence.NEXTVAL)), and that syncs up the nextval.

You can temporarily increase the cache size and do one dummy select and then reset the cache size back to 1. So for example
ALTER SEQUENCE mysequence INCREMENT BY 100;
select mysequence.nextval from dual;
ALTER SEQUENCE mysequence INCREMENT BY 1;

In my case I have a sequence called PS_LOG_SEQ which had a LAST_NUMBER = 3920.
I then imported some data from PROD to my local machine and inserted into the PS_LOG table. Production data had more than 20000 rows with the latest LOG_ID (primary key) being 20070. After importing I tried to insert new rows in this table but when saving I got an exception like this one:
ORA-00001: unique constraint (LOG.PS_LOG_PK) violated
Surely this has to do with the Sequence PS_LOG_SEQ associated with the PS_LOG table. The LAST_NUMBER was colliding with data I imported which had already used the next ID value from the PS_LOG_SEQ.
To solve that I used this command to update the sequence to the latest \ max(LOG_ID) + 1:
alter sequence PS_LOG_SEQ restart start with 20071;
This command reset the LAST_NUMBER value and I could then insert new rows into the table. No more collision. :)
Note: this alter sequence command is new in Oracle 12c.
Note: this blog post documents the ALTER SEQUENCE RESTART option does exist, but as of 18c, is not documented; it is apparently intended for internal Oracle use.

These two procedures let me reset the sequence and reset the sequence based on data in a table (apologies for the coding conventions used by this client):
CREATE OR REPLACE PROCEDURE SET_SEQ_TO(p_name IN VARCHAR2, p_val IN NUMBER)
AS
l_num NUMBER;
BEGIN
EXECUTE IMMEDIATE 'select ' || p_name || '.nextval from dual' INTO l_num;
-- Added check for 0 to avoid "ORA-04002: INCREMENT must be a non-zero integer"
IF (p_val - l_num - 1) != 0
THEN
EXECUTE IMMEDIATE 'alter sequence ' || p_name || ' increment by ' || (p_val - l_num - 1) || ' minvalue 0';
END IF;
EXECUTE IMMEDIATE 'select ' || p_name || '.nextval from dual' INTO l_num;
EXECUTE IMMEDIATE 'alter sequence ' || p_name || ' increment by 1 ';
DBMS_OUTPUT.put_line('Sequence ' || p_name || ' is now at ' || p_val);
END;
CREATE OR REPLACE PROCEDURE SET_SEQ_TO_DATA(seq_name IN VARCHAR2, table_name IN VARCHAR2, col_name IN VARCHAR2)
AS
nextnum NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT MAX(' || col_name || ') + 1 AS n FROM ' || table_name INTO nextnum;
SET_SEQ_TO(seq_name, nextnum);
END;

If you can count on having a period of time where the table is in a stable state with no new inserts going on, this should do it (untested):
DECLARE
last_used NUMBER;
curr_seq NUMBER;
BEGIN
SELECT MAX(pk_val) INTO last_used FROM your_table;
LOOP
SELECT your_seq.NEXTVAL INTO curr_seq FROM dual;
IF curr_seq >= last_used THEN EXIT;
END IF;
END LOOP;
END;
This enables you to get the sequence back in sync with the table, without dropping/recreating/re-granting the sequence. It also uses no DDL, so no implicit commits are performed. Of course, you're going to have to hunt down and slap the folks who insist on not using the sequence to populate the column...

With oracle 10.2g:
select level, sequence.NEXTVAL
from dual
connect by level <= (select max(pk) from tbl);
will set the current sequence value to the max(pk) of your table (i.e. the next call to NEXTVAL will give you the right result); if you use Toad, press F5 to run the statement, not F9, which pages the output (thus stopping the increment after, usually, 500 rows).
Good side: this solution is only DML, not DDL. Only SQL and no PL-SQL.
Bad side : this solution prints max(pk) rows of output, i.e. is usually slower than the ALTER SEQUENCE solution.

Today, in Oracle 12c or newer, you probably have the column defined as GENERATED ... AS IDENTITY, and Oracle takes care of the sequence itself.
You can use an ALTER TABLE Statement to modify "START WITH" of the identity.
ALTER TABLE tbl MODIFY ("ID" NUMBER(13,0) GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 3580 NOT NULL ENABLE);

In my case I used an approach to reset sequence to zero and than setting from zero to max of target table:
DECLARE
last_val NUMBER;
next_val NUMBER;
BEGIN
SELECT MAX(id_field) INTO next_val FROM some_table;
IF next_val > 0 THEN
SELECT some_table_seq.nextval INTO last_val FROM DUAL;
EXECUTE IMMEDIATE 'ALTER SEQUENCE some_table_seq INCREMENT BY -' || last_val || ' MINVALUE 0';
SELECT some_table_seq.nextval INTO last_val FROM DUAL;
EXECUTE IMMEDIATE 'ALTER SEQUENCE some_table_seq INCREMENT BY ' || next_val;
SELECT some_table_seq.nextval INTO last_val FROM DUAL;
EXECUTE IMMEDIATE 'ALTER SEQUENCE some_table_seq INCREMENT BY 1 MINVALUE 1';
END IF;
END;

Apologies for not having a one-liner solution, since my program runs in Typeorm with node-oracle. However, I think the following SQL commands would help with this problem.
Get the LAST_NUMBER from your sequence.
SELECT SEQUENCE_NAME, LAST_NUMBER FROM ALL_SEQUENCES WHERE SEQUENCE_NAME = '${sequenceName}'
Get the value of your PK from the last row (in this case ID is the PK).
SELECT ID FROM ${tableName} ORDER BY ID DESC FETCH NEXT 1 ROWS ONLY
Lastly update LAST_NUMBER to the value + 1:
ALTER SEQUENCE ${sequenceName} RESTART START WITH ${value + 1}

Related

Q:reset sequnce, help around understanding

I do not understand why there is a select statement in this code and why it has twice the alter sequence statement for increment by
I found this code on the site asktom
CREATE OR REPLACE
PACKAGE pkg_seq AS
PROCEDURE alterSequenceLast(
sequenceName IN VARCHAR2,
inc in integer);
END;
/
CREATE OR REPLACE
PACKAGE BODY pkg_asktom AS
PROCEDURE alterSequenceLast(
sequenceName IN VARCHAR2,
inc in integer)
is
stmt VARCHAR2(2000);
l_n number;
begin
BEGIN
-- alter increment to inc
stmt := 'ALTER SEQUENCE ' || sequenceName ||' INCREMENT BY ' ||inc;
dbms_output.put_line('Executing ''' || stmt || '''');
EXECUTE IMMEDIATE stmt;
-- read the next value
stmt := 'SELECT ' || sequenceName || '.NEXTVAL FROM DUAL';
dbms_output.put_line('Executing ''' || stmt || '''');
EXECUTE IMMEDIATE stmt into l_n;
-- alter increment to 1
stmt := 'ALTER SEQUENCE ' || sequenceName ||' INCREMENT BY 1';
dbms_output.put_line('Executing ''' || stmt || '''');
EXECUTE IMMEDIATE stmt;
END;
end alterSequenceLast;
END;
/
After execution I get this
SQL> exec pkg_seq.alterSequenceLast('acc_seq',1);
Executing 'ALTER SEQUENCE acc_seq INCREMENT BY 1'
Executing 'SELECT acc_seq.NEXTVAL FROM DUAL'
Executing 'ALTER SEQUENCE acc_seq INCREMENT BY 1'
I would do it this way, I would return the sequence to the beginning.
CREATE OR REPLACE
PACKAGE pkg_seq AS
PROCEDURE alterSequenceLast(
sequenceName IN VARCHAR2,
inc in integer);
END;
/
CREATE OR REPLACE
PACKAGE BODY pkg_seq AS
PROCEDURE alterSequenceLast(
sequenceName IN VARCHAR2,
inc in integer)
is
stmt VARCHAR2(2000);
l_n number;
begin
BEGIN
stmt := 'ALTER SEQUENCE ' || sequenceName ||' restart start with ' ||inc;
dbms_output.put_line('Executing ''' || stmt || '''');
EXECUTE IMMEDIATE stmt;
-- alter increment to 1
stmt := 'ALTER SEQUENCE ' || sequenceName ||' INCREMENT BY 1';
dbms_output.put_line('Executing ''' || stmt || '''');
EXECUTE IMMEDIATE stmt;
END;
end alterSequenceLast;
END;
/
Thaks advance, Petar.
This code is for setting sequence to start from a new value.
For instance, suppose we have a table which usually has its ID generated by the sequence our_seq but for some reason we have loaded 10000 records without using the sequence. What happens if we insert a new record? As it stands our_seq.nextval will generate an ID which clashes with the key of a bulk-loaded record.
So, we need to adjust the value of the sequence. One way to do that would be to select nextval ten thousand times. Or we could
alter the sequence to increment by 10000
select nextval for the sequence once
alter the sequence to increment by 1 once again
The Tom Kyte package is a way of automating the second approach. Personally I would regard it as an interesting curio. If you need to undertake this operation often enough to need to automate it you probably should re-consider your data loading practices.
could [we] increase the sequence for 10001 without select a statement?
No. The only supported way to do that would be to drop the sequence and re-create it with the required start with value. Dropping an object is more complicated because of grants, code invalidations, etc.
Supposing your interest is you have a sequence of say current value = 10001 and you want to reset it so it starts from 1 again you would pass a negative increment -10001. Be careful you don't decrement it past the minvalue (by default 1); the database won't warn you or hurl until you issue a nextval at which point it will tell you:
ORA-08004: sequence OUR_SEQ.NEXTVAL goes below MINVALUE and cannot be instantiated

How to change the sequence order without dropping the sequence in oracle

my current sequence no is 203 i need to jump to 1203 without dropping the sequence.
First alter the sequence increment, given you stated 203 to 1203, I'm adding 1000, adjust as appropriate.
ALTER SEQUENCE yourSequence INCREMENT BY 1000;
Then request a value
SELECT yourSequence.NextVal FROM dual;
Then alter it back to incrementing by 1 (assuming it was 1 in the first place)
ALTER SEQUENCE yourSequence INCREMENT BY 1;
You really don't want to do this if the sequence is in active use - since it could jump up multiple thousands.
You may create such a procedure on your desired schema of DB as follows :
SQL>Create or Replace Procedure Pr_Set_Sequence( i_seq_name varchar2, i_val pls_integer ) is
v_val pls_integer;
begin
for c in (
Select u.sequence_name seq
From User_Sequences u
Where u.sequence_name = upper(i_seq_name)
)
loop
execute immediate 'select '||i_seq_name||'.nextval from dual' INTO v_val;
execute immediate 'alter sequence '||i_seq_name||' increment by ' ||
to_char(-v_val+i_val) || ' minvalue 0';
execute immediate 'select '||i_seq_name||'.nextval from dual' INTO v_val;
execute immediate 'alter sequence '||i_seq_name||' increment by 1 minvalue 0';
end loop;
end;
and call with the desired value ( in my case a create a new one called my_seq ) :
SQL> create sequence my_seq;
Sequence created
SQL> select my_seq.nextval from dual;
NEXTVAL
----------
1
SQL> begin
2 pr_set_sequence('my_seq',1203);
3 end;
4 /
PL/SQL procedure successfully completed
SQL> select my_seq.currval from dual;
NEXTVAL
----------
1203
Just do a WHILE loop, selecting from the sequence until it's up to the value you need. It's quite fast.
declare
v_sequence_value number;
begin
while v_sequence_value <= 1203 loop
select my_sequence.nextval into v_sequence_value from dual;
end loop;
end;
/

Oracle procedure/function to create a trigger in table

I'm trying to create a procedure that given a table name, it will create a sequence and auto incrementing trigger, all using variables based on the table name.
Code :
CREATE OR REPLACE procedure CREATE_SEQUENTIAL_TR(table_name VARCHAR)
is -- Tried using declare but it wouldn't accept
coluna_cod varchar(100 char);
begin
--Finding the cod column name for this table first
--They start with "PK_CD"
select
COLUMN_NAME
into
coluna_cod
from
ALL_TAB_COLUMNS
where
TABLE_NAME=table_name
and COLUMN_NAME like "PK_CD%";
--Creating the sequence obj
drop sequence "cod" || table_name;
create sequence "cod" || table_name;
--Now creating the trigger
create or replace trigger "cod" || table_name || "tr"
before
UPDATE or INSERT on table_name
for each row
declare
cod number := coluna_cod;
tr_name varchar(100 char) := "cod" || table_name
begin
if UPDATING then
if :new.cod != :old.cod then
:new.cod := :old.cod;
end if;
else -- inserting
:new.cod := tr_name.nextval();
end if;
end;
end;
The complexity of this ended up quite out of the scope of my knowledge.
At the moment it is giving an error on drop sequence "cod" || table_name (Unexpected DROP symbol found) but I'm sure I have made other errors.
Can someone help me figure this logic out?
You can't put DDL statements (like drop or create or alter) directly inside a PL/SQL block. If you want to do DDL inside PL/SQL, you can do an execute immediate:
declare
begin
drop sequence X; -- error
execute immediate 'drop sequence X'; -- works fine
end;
/

Best way to generate unique and consecutive numbers in Oracle

I need to generate unique and consecutive numbers (for use on an invoice), in a fast and reliable way. I currently use an Oracle sequence, but in some cases, the generated numbers are not consecutive because of exceptions that may occur.
I thought a couple of solutions to manage this problem, but neither of them convincing me. What solution do you recommend?
Use a select max ()
SELECT MAX (NVL (doc_num, 0)) +1 FROM invoices
Use a table to store the last number generated for the invoice.
UPDATE docs_numbers
SET last_invoice = last_invoice + 1
Another Solution?
Option 1 can always be made to fail some way in an environment with concurrent
users.
Option 2 will work, but will limit scalability — obligatory Tom Kyte
reference:
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1508205334476
As he recommends, you should really review the necessity for the "no gaps" requirement.
The gaps appear if a transaction uses a sequence number but is then rolled back.
Maybe the answer is not to assign the invoice number until the invoice can't be rolled back. This minimizes (but probably does not eliminate) the possibilities of gaps.
I'm not sure that there is any swift or easy way to ensure no gaps in the sequence - scanning for MAX, adding one, and inserting that is probably the closest to secure, but is not recommended for performance reasons (and difficulties with concurrency) and the technique won't detect if the latest invoice number is assigned, then deleted and reassigned.
Can you account for gaps somehow - by identifying which invoice numbers were 'used' but 'not made permanent' somehow? Could an autonomous transaction help in doing that?
Another possibility - assuming that gaps are relatively few and far between.
Create a table that records sequence numbers that must be reused before a new sequence value is grabbed. Normally, it would be empty, but some process that runs every ... minute, hour, day ... checks for gaps and inserts the missed values into this table. All processes first check the table of missed values, and if there are any present, use a value from there, going through the slow process of updating the table and removing the row that they use. If the table is empty, then grab the next sequence number.
Not very pleasant, but the decoupling of 'issuing invoice numbers' from 'scan for missed values' means that even if the invoicing process fails for some thread when it is using one of the missed values, that value will be rediscovered to be missing and re-reissued next time around - repeating until some process succeeds with it.
Keep the current sequence - you can use the following to reset the value to the maximum of what is currently stored in the table(s):
-- --------------------------------
-- Purpose..: Resets the sequences
-- --------------------------------
DECLARE
-- record of temp data table
TYPE data_rec_type IS RECORD(
sequence_name VARCHAR2(30),
table_name VARCHAR2(30),
column_name VARCHAR2(30));
-- temp data table
TYPE data_table_type IS TABLE OF data_rec_type INDEX BY BINARY_INTEGER;
v_data_table data_table_type;
v_index NUMBER;
v_tmp_id NUMBER;
-- add row to temp table for later processing
--
PROCEDURE map_seq_to_col(in_sequence_name VARCHAR2,
in_table_name VARCHAR2,
in_column_name VARCHAR2) IS
v_i_index NUMBER;
BEGIN
v_i_index := v_data_table.COUNT + 1;
v_data_table(v_i_index).sequence_name := in_sequence_name;
v_data_table(v_i_index).table_name := in_table_name;
v_data_table(v_i_index).column_name := in_column_name;
END;
/**************************************************************************
Resets a sequence to a given value
***************************************************************************/
PROCEDURE reset_seq(in_seq_name VARCHAR2, in_new_value NUMBER) IS
v_sql VARCHAR2(2000);
v_seq_name VARCHAR2(30) := in_seq_name;
v_reset_val NUMBER(10);
v_old_val NUMBER(10);
v_new_value NUMBER(10);
BEGIN
-- get current sequence value
v_sql := 'SELECT ' || v_seq_name || '.nextval FROM DUAL';
EXECUTE IMMEDIATE v_sql
INTO v_old_val;
-- handle empty value
v_new_value := in_new_value;
if v_new_value IS NULL then
v_new_value := 0;
END IF;
IF v_old_val <> v_new_value then
IF v_old_val > v_new_value then
-- roll backwards
v_reset_val := (v_old_val - v_new_value) * -1;
elsif v_old_val < v_new_value then
v_reset_val := (v_new_value - v_old_val);
end if;
-- make the sequence rollback to 0 on the next call
v_sql := 'alter sequence ' || v_seq_name || ' increment by ' ||
v_reset_val || ' minvalue 0';
EXECUTE IMMEDIATE (v_sql);
-- select from the sequence to make it roll back
v_sql := 'SELECT ' || v_seq_name || '.nextval FROM DUAL';
EXECUTE IMMEDIATE v_sql
INTO v_reset_val;
-- make it increment correctly again
v_sql := 'alter sequence ' || v_seq_name || ' increment by 1';
EXECUTE IMMEDIATE (v_sql);
-- select from it again to prove it reset correctly.
v_sql := 'SELECT ' || v_seq_name || '.currval FROM DUAL';
EXECUTE IMMEDIATE v_sql
INTO v_reset_val;
END IF;
DBMS_OUTPUT.PUT_LINE(v_seq_name || ': ' || v_old_val || ' to ' ||
v_new_value);
END;
/*********************************************************************************************
Retrieves a max value for a table and then calls RESET_SEQ.
*********************************************************************************************/
PROCEDURE reset_seq_to_table(in_sequence_name VARCHAR2,
in_table_name VARCHAR2,
in_column_name VARCHAR2) IS
v_sql_body VARCHAR2(2000);
v_max_value NUMBER;
BEGIN
-- get max value in the table
v_sql_body := 'SELECT MAX(' || in_column_name || '+0) FROM ' ||
in_table_name;
EXECUTE IMMEDIATE (v_sql_body)
INTO v_max_value;
if v_max_value is null then
-- handle empty tables
v_max_value := 0;
end if;
-- use max value to reset the sequence
RESET_SEQ(in_sequence_name, v_max_value);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Failed to reset ' || in_sequence_name ||
' from ' || in_table_name || '.' ||
in_column_name || ' - ' || sqlerrm);
END;
BEGIN
--DBMS_OUTPUT.ENABLE(1000000);
-- load sequence/table/column associations
/***** START SCHEMA CUSTOMIZATION *****/
map_seq_to_col('Your_SEQ',
'your_table',
'the_invoice_number_column');
/***** END SCHEMA CUSTOMIZATION *****/
-- iterate all sequences that require a reset
FOR v_index IN v_data_table.FIRST .. v_data_table.LAST LOOP
BEGIN
RESET_SEQ_TO_TABLE(v_data_table(v_index).sequence_name,
v_data_table(v_index).table_name,
v_data_table(v_index).column_name);
END;
END LOOP;
END;
/
-- -------------------------------------------------------------------------------------
-- End of Script.
-- -------------------------------------------------------------------------------------
The example is an anonymous sproc - change it to be proper procedures in a package, and call it prior to inserting a new invoice to keep the numbering consistent.
I think you'll find that using the MAX() of the existing numbers is prone to a new and exciting problem - duplicates can occur if multiple invoices are being created at the same time. (Don't ask me how I know...).
A possible solution is to derive the primary key on your INVOICE table from a sequence, but have this NOT be the invoice number. After correctly and properly creating your invoice, and after the point at which an exception or user's whim could cause the creation of the invoice to be terminated, you go to a second sequence to get the sequential number which is presented as "the" invoice number. This means you'll have two unique, non-repeating numbers on your INVOICE table, and the obvious one (INVOICE_NO) will not be the primary key (but it can and should be UNIQUE) so there's a bit of evil creeping in, but the alternative - which is to create the INVOICE row with one value in the primary key, then change the primary key after the INVOICE is created - is just too evil for words. :-)
Share and enjoy.
If you really want to have no gaps, you need to completely serialize access, otherwise there will always be gaps. The reasons for gaps are:
rollback
shutdown abort
It's not clear what you mean by 'because of exceptions that may occur'. If you want number NOT to be incremented if your transaction eventually rolls back then SEQUENCE is not going to work for you, because as far as I know, once NEXTVAL is requested from sequence the sequence position is incremented and rollback won't reverse it.
If this is indeed a requirements then you probably would have to resort of storing current counter in a separate table, but beware of concurrent updates - from both 'lost update' and scalability prospective.
I've come across this problem before. In one case, we were able to convince the business to accept that "real" invoices might have gaps, and we wrote a job that ran every day to "fill in" the gaps with "void" invoices for audit purposes.
In practice, if we put NOCACHE on the sequence, the number of gaps would be relatively low, so the auditors will usually be happy as long as their query on the "void" invoices don't return too many results.
You might have to re-think your process slighty and break it into more steps. Have one non-transactional step create the placeholder invoice (this not being in the transaction should eliminate gaps) and then within the transaction do the rest of your business. I think that was how we did it in a system I was stuck with years ago but I can't remember - I just remember it was "weird."
I'd say the sequence will guarantee unique/consecutive numbers but when you throw transactions in the mix that can't be guaranteed unless the sequence generation isn't within that transaction.
dpbradley's link in #2 sounds like your best bet. Tom keeps the transactionality with the caller, if you don't want that you could make it an autonomous transaction like so:
create or replace
function getNextInvoiceNumber()
return number is
l_invoicenum number;
pragma autonomous_transaction;
begin
update docs_numbers
set last_invoice = last_invoice + 1
returning last_invoice
into l_invoicenum;
commit;
return l_invoicenum;
exception
when others then
rollback;
raise;
end;
What we do is issue a sequence number to the transaction and then when the item we are processing is finalized we issue a permanent number (also a sequence).
Works well for us.
Regards
K

How to create an Oracle sequence starting with max value from a table?

Trying to create a sequence in Oracle that starts with the max value from a specific table. Why does this not work?
CREATE SEQUENCE transaction_sequence
MINVALUE 0
START WITH (SELECT MAX(trans_seq_no)
FROM TRANSACTION_LOG)
INCREMENT BY 1
CACHE 20;
If you can use PL/SQL, try (EDIT: Incorporates Neil's xlnt suggestion to start at next higher value):
SELECT 'CREATE SEQUENCE transaction_sequence MINVALUE 0 START WITH '||MAX(trans_seq_no)+1||' INCREMENT BY 1 CACHE 20'
INTO v_sql
FROM transaction_log;
EXECUTE IMMEDIATE v_sql;
Another point to consider: By setting the CACHE parameter to 20, you run the risk of losing up to 19 values in your sequence if the database goes down. CACHEd values are lost on database restarts. Unless you're hitting the sequence very often, or, you don't care that much about gaps, I'd set it to 1.
One final nit: the values you specified for CACHE and INCREMENT BY are the defaults. You can leave them off and get the same result.
Here I have my example which works just fine:
declare
ex number;
begin
select MAX(MAX_FK_ID) + 1 into ex from TABLE;
If ex > 0 then
begin
execute immediate 'DROP SEQUENCE SQ_NAME';
exception when others then
null;
end;
execute immediate 'CREATE SEQUENCE SQ_NAME INCREMENT BY 1 START WITH ' || ex || ' NOCYCLE CACHE 20 NOORDER';
end if;
end;
you might want to start with max(trans_seq_no) + 1.
watch:
SQL> create table my_numbers(my_number number not null primary key);
Table created.
SQL> insert into my_numbers(select rownum from user_objects);
260 rows created.
SQL> select max(my_number) from my_numbers;
MAX(MY_NUMBER)
--------------
260
SQL> create sequence my_number_sn start with 260;
Sequence created.
SQL> insert into my_numbers(my_number) values (my_number_sn.NEXTVAL);
insert into my_numbers(my_number) values (my_number_sn.NEXTVAL)
*
ERROR at line 1:
ORA-00001: unique constraint (NEIL.SYS_C00102439) violated
When you create a sequence with a number, you have to remember that the first time you select against the sequence, Oracle will return the initial value that you assigned it.
SQL> drop sequence my_number_sn;
Sequence dropped.
SQL> create sequence my_number_sn start with 261;
Sequence created.
SQL> insert into my_numbers(my_number) values (my_number_sn.NEXTVAL);
1 row created.
If you're trying to do the 'gapless' thing, I strongly advise you to
1 not do it, and #2 not use a sequence for it.
You can't use a subselect inside a CREATE SEQUENCE statement. You'll have to select the value beforehand.
Bear in mid, the MAX value will only be the maximum of committed values. It might return 1234, and you may need to consider that someone has already inserted 1235 but not committed.
Based on Ivan Laharnar with less code and simplier:
declare
lastSeq number;
begin
SELECT MAX(ID) + 1 INTO lastSeq FROM <TABLE_NAME>;
if lastSeq IS NULL then lastSeq := 1; end if;
execute immediate 'CREATE SEQUENCE <SEQUENCE_NAME> INCREMENT BY 1 START WITH ' || lastSeq || ' MAXVALUE 999999999 MINVALUE 1 NOCACHE';
end;
DECLARE
v_max NUMBER;
BEGIN
SELECT (NVL (MAX (<COLUMN_NAME>), 0) + 1) INTO v_max FROM <TABLE_NAME>;
EXECUTE IMMEDIATE 'CREATE SEQUENCE <SEQUENCE_NAME> INCREMENT BY 1 START WITH ' || v_max || ' NOCYCLE CACHE 20 NOORDER';
END;
use dynamic sql
BEGIN
DECLARE
maxId NUMBER;
BEGIN
SELECT MAX(id)+1
INTO maxId
FROM table_name;
execute immediate('CREATE SEQUENCE sequane_name MINVALUE '||maxId||' START WITH '||maxId||' INCREMENT BY 1 NOCACHE NOCYCLE');
END;
END;