I have a table in SQL Server. Its PK, ID, is of type INT and is auto-incremented. The task I have is to get this table in a Firebird database (version 4.0.4). Remarks: we have a website that uses this DB where pages have a host of references to those IDs, so they should be kept as they are.
I'm using simple console program in .NET 6 (C#) to select and insert data using ADO.NET provider. My question is if I should first import data to the Firebird database and then add a generator as auto-increment to that table, considering the fact that generator should use the maximum value of PK of the existing data as its starting value. As far as I understand there at least two things to care about:
- If I create auto-increment first and insert data (select without ID column), those IDs will start from 1.
- I am not sure what will be the result of that generator's action when it starts from a default 0 value. Suppose there are IDs 2, 4, 5, 8. On next insert it creates 1 as ID. And what will be the outcome of the second one? ++1 gives 2 which is already in use, so we are stuck.
Am I on a correct way? Correct me if I am wrong.
If you would use a sequence (a.k.a. generator) and not modify its current value after a bulk insert which inserts with explicit ID values, then for subsequent inserts, the sequence will generate values 1, 2, 3, 4, etc.. So, if such a value already happens to exist, you'll get duplicate values. What happens depends on whether or not the column has a primary key or unique key constraint: if there is no constraint, a duplicate ID value would be inserted, if there is a constraint, the insert will fail.
There are multiple ways to go about this. Since Firebird 3.0, Firebird has "real" identity columns, which means you don't have to fiddle with sequences and triggers yourself. The downside is that it is not possible to add an identity column to a table which already has rows, and it is not possible to convert an existing column to an identity column.
Given you're using Firebird 4.0, I would recommend doing the following:
Create the table with a
GENERATED ALWAYS AS IDENTITY
column. You can also useGENERATED BY DEFAULT AS IDENTITY
, but IMHO you should only do that if you want to be able to override the identity in general:If you used
GENERATED ALWAYS
in the previous step, use theOVERRIDING SYSTEM VALUE
in yourINSERT
statement. That way you can insert an explicit value into a generated always identity column.If you used
GENERATED BY DEFAULT
, you don't need theOVERRIDING
clause (allowing the insert to specify the identity column value is default behaviour for generated by default).After inserting all rows, change the next value of the identity column to the current maximum + 1 (not to the current maximum!).
First obtain the maximum + 1 value:
Make sure you execute this while no other transactions insert records in this table.
Use the obtained value in
ALTER TABLE .. RESTART WITH ...
, sayNEXT_VALUE
was 1923:Alternatively, although formally not supported, you could use PSQL code to run this as one statement:
NOTE: Use of
SET TERM
is only needed for interactive execution, like when using ISQL. Firebird itself doesn't support this command. If you execute this, for example, from C#, you just need to execute theexecute block ... end
(without the#
!).From now on, insert records into this table without specifying the
ID
column (and if you do, and used generated always you'll get an error unlessOVERRIDING SYSTEM VALUE
has been specified).If you insist on using sequences and triggers, you can do the following:
Create the table with a normal integer column:
Insert the rows:
After inserting the rows, define the sequence and auto-increment trigger:
First obtain the next value:
Then create the sequence and trigger (assume
NEXT_VALUE
was 1923):NOTE: This trigger will unconditionally generate an ID. You may want to consider raising an exception if
NEW.ID
is not null, or to only generate an ID ifNEW.ID
is null. See my answer here for a conditional alternative.Although you could use the PSQL trick to set the sequence value dynamically, I would recommend to first create the sequence (leave off the
START WITH
clause) and trigger, commit the transaction, and then use anEXECUTE BLOCK
withALTER SEQUENCE ... RESTART WITH ...
if you want to go that route.