SQL Server Identity Columns
SQL
Server IDENTITY columns are numeric columns which are set to auto
increment when new values are inserted into the table. These columns
are great for uniquely identifying records in a table, especially when
you do not have any natural key to define a primary key constraint on
the table. Like a primary key, there can only be one IDENTITY property
defined on a column in a table.
An
IDENTITY column has a seed value (where the values begin) and the
numeric increment value for each new record inserted. When new values
are inserted into the table, the new valued will be based on the
increment value of the identity column. The first value inserted into
the table will have the value of seed value as specified.
The following data types can have the IDENTITY property set for them: decimal, int, numeric, smallint, bigint, and tinyint.
One
potential issue you may run into when using IDENTITY columns is when
you are merging data from two or more tables into one common table.
Because the tables which contain the merged IDENTITY columns may
overlap values, it is important to define what may be required up front
so this is avoided. For example, if you have a table from SystemA and a
table from SystemB which will be combined and used in SystemC, it is
critical that the IDENTITY values from the twosystems are defined so that they do not overlap when they are combined in SystemC.
The following script creates a table named Article, which includes an IDENTITY field named ArticleID.
CREATE TABLE Articles
(
ArticleID INT IDENTITY(1,1),
Title VARCHAR(255) NOT NULL,
AuthorID INT NOT NULL,
CreationDate SMALLDATETIME,
CONSTRAINT pk_Articles_ArticleID PRIMARY KEY CLUSTERED(ArticleID ASC)
)
When
I insert into the Articles table, I omit supplying the ArticleID field
in the INSERT list, as SQL Server determines and inserts this value for
me. It is worth noting here that while it is not a great idea to omit
the INSERT field list, you may do so and still supply the same column list and SQL Server will determine that it needs to increment the identity value.
INSERT INTO Articles
(
Title, AuthorID, CreationDate
)
SELECT 'Identity Columns', 1, GETDATE()
SELECT SCOPE_IDENTITY(), @@IDENTITY
It
is easy to determine the current identity increment value for a table
which contains an IDENTITY column. The IDENT_CURRENT returns the
current IDENTITY increment value for a specific table.
SELECT IDENT_CURRENT('Articles')
I can also use a DBCC command to return the current increment value for an IDENTITY column.
DBCC CHECKIDENT ('Articles')
To
determine if a particular table contains a column with the IDENTITY
property defined for it, you can use the following function:
SELECT OBJECTPROPERTY (OBJECT_ID('Articles'),'TableHasIdentity')
A
value of 1 returned by the above functions indicates that the table
does have an IDENTITY column. Consequently, you can use the following
script to query the SQL Server system views to determine if the table
contains an IDENTITY column.
SELECT
CASE WHEN EXISTS
(
SELECT
1
FROM
sys.tables t
JOIN sys.columns c on t.object_id = c.object_id
WHERE
t.name = 'Articles'
AND c.is_identity = 1
) THEN 1
ELSE 0
END
IDENTITY Columns in Transactions
One
interesting thing about IDENTITY columns are that when INSERT
statements are rolled back, the current increment value of the IDENTITY
column is NOT rolled back. Each execution of the following script
increments the IDENTITY value, even though the actual operation is not
committed.
BEGIN TRANSACTION
INSERT INTO Articles
(
Title, AuthorID, CreationDate
)
SELECT 'Identity Columns', 1, GETDATE()
SELECT SCOPE_IDENTITY(), @@IDENTITY
ROLLBACK TRANSACTION
I can check the current increment value of the IDENITITY column value on the Articles table using the IDENT_CURRENT function.
SELECT IDENT_CURRENT('Articles')
If
I want to reset the increment value for the table, I can use the DBCC
CHECKIDENT command to reseed the increment value. I use 1 as the reseed
value because that was the last value I inserted into the table. The
next value inserted will be 2.
DBCC CHECKIDENT ('Articles', 'RESEED', 1)
In
the above INSERT examples, I returned the values from @@IDENTITY and
SCOPE_IDENTITY() to view the last IDENTITY value inserted. So, what is
the difference between them? Well, the answer is the scope in which the
INSERT occurs. In the above example, both values are the same because
the INSERT action only occurs on one table. However, if the Articles
table had a trigger on it, and that trigger inserts a new value into a
table which also has an IDENTITY column, the values can be different.
In the following example an article table is defined for the Articles
table and a a trigger is defined to archive inserted or updated
records.
CREATE TABLE ArticlesArchive
(
ArchiveID INT IDENTITY(1000, 1),
ArticleID INT,
Title VARCHAR(255) NOT NULL,
AuthorID INT NOT NULL,
CreationDate SMALLDATETIME,
ArchiveDate SMALLDATETIME
CONSTRAINT pk_ArticlesArchive_ArchiveID PRIMARY KEY CLUSTERED (ArchiveID ASC)
)
CREATE TRIGGER tr_Articles_ArchiveData ON Articles
AFTER INSERT, UPDATE
AS
BEGIN
INSERT INTO ArticlesArchive
(
ArticleID,
Title,
AuthorID,
CreationDate,
ArchiveDate
)
SELECT
ArticleID,
Title,
AuthorID,
CreationDate,
GETDATE()
FROM
INSERTED
END
Now, if the same INSERT statement from above is ran, the outcome is drastically different.
INSERT INTO Articles
(
Title, AuthorID, CreationDate
)
SELECT 'Identity Columns 2', 1, GETDATE()
SELECT SCOPE_IDENTITY(), @@IDENTITY
This
time, the SCOPE_IDENTITY () function returns the value 2 (the value 2
is returned because we previously entered a record into the Articles),
and the @@IDENTITY value returns 1000 (which is the seed value from the
ArticlesArchive table). As you can see, the SCOPE_IDENTITY () function
returns the last IDENTITY value inserted in the current scope only.
@@IDENTITY returns the last IDENTITY value inserted in the current
session. The action of the trigger is out of the current scope of the
operation, but is still bound by the current session. For this reason,
it is important you know the functionality of these two values when
programming.
In
the above examples, I focused on retrieving the new IDENTITY values for
single-record inserts. I can, however, use the OUTPUT clause
(available in SQL Server 2005 and 2008) to capture the new IDENTITY
values or a batch of inserted records.
CREATE TABLE #IdentityOutput (ArticleID INT)
INSERT INTO Articles
(
Title,
AuthorID,
CreationDate
)
OUTPUT
INSERTED.ArticleID
INTO #IdentityOutput (ArticleID)
SELECT
'Temp Tables',
4,
GETDATE()
UNION ALL
SELECT
'Views',
5,
GETDATE()
UNION ALL
SELECT
'Stored Procedures',
6,
GETDATE()
UNION ALL
SELECT
'Constraints',
7,
GETDATE()
UNION ALL
SELECT
'Database Mail',
8,
GETDATE()
SELECT *
FROM #IdentityOutput
Using the OUTPUT method, you can easily capture all new IDENTITY column values and then use them in subsequent operations. This allows you to get away from the iterative IDENTITY capture approach and from having to query the table right after you have inserted the new values.
Wrap Up
Today I looked at the different methods you can use to capture IDENTITY values after they've been inserted into a SQL Server table. Capturing these values is crucial to many applications, and the more efficiently you can capture the values, the better.
Posted
23 Feb 2009 7:34 PM
by
TimChapman