IDENTITY, sequences and generated keys
How IDENTITY numbers rows, why gaps are normal, how to get the id you just inserted, when a SEQUENCE is the better tool, and how to copy rows with their original ids.
IDENTITY(seed, increment) makes SQL Server number new rows: the first gets the seed, each later one adds the increment. Plain IDENTITY means IDENTITY(1, 1). The counter belongs to the table, and SQL Server makes sure no two rows ever get the same number, even when many users insert at once.
Gaps are normal
CREATE TABLE dbo.Notes ( NoteId int NOT NULL IDENTITY(1, 1) PRIMARY KEY, Body nvarchar(20) NOT NULL);INSERT INTO dbo.Notes (Body) VALUES (N'first'), (N'second');INSERT INTO dbo.Notes (Body) VALUES (NULL); -- fails: Body is NOT NULLGOINSERT INTO dbo.Notes (Body) VALUES (N'third');DELETE FROM dbo.Notes WHERE NoteId = 4;INSERT INTO dbo.Notes (Body) VALUES (N'fourth'); SELECT * FROM dbo.Notes;Msg 515, Level 16, State 2, Line 6
Cannot insert the value NULL into column 'Body', table 'BuildMart.dbo.Notes'; column does not allow nulls. INSERT fails.
NoteId Body
------ ------
1 first
2 second
5 fourthWhich id did I just get?
SCOPE_IDENTITY() returns the last IDENTITY value made by your own statement in your own scope:
INSERT INTO dbo.Orders (CustomerId, OrderDate, Status)VALUES (7, '2024-07-02', 'Pending'); DECLARE @OrderId int = SCOPE_IDENTITY(); INSERT INTO dbo.OrderItems (OrderId, ProductId, Quantity, UnitPrice)VALUES (@OrderId, 1, 1, 18.50), (@OrderId, 3, 1, 9.75); SELECT OrderId, ProductId, Quantity FROM dbo.OrderItems WHERE OrderId = @OrderId;OrderId ProductId Quantity
------- --------- --------
11 1 1
11 3 1DECLARE @OrderId int = … creates a variable to hold the id for the next statement; variables get their own lesson in the Programmability module.
| Function | Returns | Safe? |
|---|---|---|
SCOPE_IDENTITY() | ||
@@IDENTITY | ||
IDENT_CURRENT('t') | ||
OUTPUT inserted.Id |
SEQUENCE: a counter of its own
NEXT VALUE FOR, and you can use it as a column’s DEFAULT, in several tables, or before you insert anything:
CREATE SEQUENCE dbo.DocumentNo AS int START WITH 5000 INCREMENT BY 1;GOCREATE TABLE dbo.Invoices ( InvoiceNo int NOT NULL CONSTRAINT DF_Invoices_No DEFAULT (NEXT VALUE FOR dbo.DocumentNo) PRIMARY KEY, OrderId int NOT NULL);CREATE TABLE dbo.CreditNotes ( CreditNo int NOT NULL CONSTRAINT DF_CreditNotes_No DEFAULT (NEXT VALUE FOR dbo.DocumentNo) PRIMARY KEY, OrderId int NOT NULL); INSERT INTO dbo.Invoices (OrderId) VALUES (1), (2);INSERT INTO dbo.CreditNotes (OrderId) VALUES (6);INSERT INTO dbo.Invoices (OrderId) VALUES (3); SELECT 'Invoice' AS Doc, InvoiceNo AS No, OrderId FROM dbo.InvoicesUNION ALLSELECT 'Credit note', CreditNo, OrderId FROM dbo.CreditNotes;Doc No OrderId ----------- ---- ------- Invoice 5000 1 Invoice 5001 2 Invoice 5003 3 Credit note 5002 6
UNION ALL stacks two results; it is covered in the Joins module.)
Copying rows with their ids
SET IDENTITY_INSERT table ON allows explicit values for one table at a time; switch it off straight afterwards:
SET IDENTITY_INSERT dbo.Products ON; INSERT INTO dbo.Products (ProductId, Name, CategoryId, Price, Stock, Unit, AddedOn)VALUES (100, N'Legacy item', 1, 1.00, 0, N'each', '2020-01-01'); SET IDENTITY_INSERT dbo.Products OFF; INSERT INTO dbo.Products (Name, CategoryId, Price, Stock, Unit, AddedOn)VALUES (N'New item', 1, 2.00, 5, N'each', '2024-07-01'); SELECT ProductId, Name FROM dbo.Products WHERE ProductId > 12;ProductId Name
--------- -----------
100 Legacy item
101 New itemdbo.TicketNo that starts at 500 and goes up by 5. Create a table dbo.SupportTickets whose TicketNo defaults to the next sequence value, with a required Subject nvarchar(80). Insert three tickets in one statement and list them.
Show a hint
Show the solution
CREATE SEQUENCE dbo.TicketNo AS int START WITH 500 INCREMENT BY 5;GOCREATE TABLE dbo.SupportTickets ( TicketNo int NOT NULL DEFAULT (NEXT VALUE FOR dbo.TicketNo) PRIMARY KEY, Subject nvarchar(80) NOT NULL); INSERT INTO dbo.SupportTickets (Subject)VALUES (N'Damaged delivery'), (N'Wrong colour paint'), (N'Invoice query'); SELECT * FROM dbo.SupportTickets;TicketNo Subject
-------- ------------------
500 Damaged delivery
505 Wrong colour paint
510 Invoice queryRemember
IDENTITY(seed, increment)numbers rows automatically; you cannot write to it unless IDENTITY_INSERT is on.Failed inserts, rollbacks and deletes leave gaps; IDENTITY is unique, not gap-free. Use SCOPE_IDENTITY()for the id of one new row andOUTPUT inserted.Idfor many; avoid@@IDENTITY.A SEQUENCE is a counter of its own: NEXT VALUE FOR, usable as a DEFAULT and shared between tables.TRUNCATE resets IDENTITY to the seed; DELETE does not.