Sequences — CREATE SEQUENCE and Application-Level ID Generation

A SEQUENCE is a database object that generates a sequential series of numeric values on demand — independent of any table. Unlike IDENTITY columns which are tied to a specific table, a sequence is standalone: multiple tables can share one sequence, you can retrieve the next value without performing an insert, and sequences support cycling, caching, and negative increments. They are the right choice when you need pre-allocated IDs, multi-table key coordination, or batch ID generation.

SEQUENCE Objects

-- ── Create a sequence ──────────────────────────────────────────────────────
CREATE SEQUENCE dbo.seq_PostId
    AS INT
    START WITH 10000      -- first value
    INCREMENT BY 1        -- step size
    MINVALUE 10000
    MAXVALUE 2147483647   -- INT max
    NO CYCLE              -- throw error when exhausted (vs CYCLE to restart)
    CACHE 50;             -- pre-allocate 50 values in memory for performance
GO

-- ── Get the next value ────────────────────────────────────────────────────
SELECT NEXT VALUE FOR dbo.seq_PostId;    -- returns 10000 first call
SELECT NEXT VALUE FOR dbo.seq_PostId;    -- returns 10001

-- ── Use sequence as a column default ─────────────────────────────────────
ALTER TABLE dbo.Posts
ADD SequenceId INT NOT NULL
    DEFAULT (NEXT VALUE FOR dbo.seq_PostId);

-- ── Shared sequence across multiple tables ────────────────────────────────
-- All related entities share a single ID space (no ID collisions):
CREATE SEQUENCE dbo.seq_ContentId AS BIGINT START WITH 1 INCREMENT BY 1;

-- Use in multiple tables:
-- CREATE TABLE dbo.Posts    (..., Id BIGINT DEFAULT NEXT VALUE FOR dbo.seq_ContentId);
-- CREATE TABLE dbo.Comments (..., Id BIGINT DEFAULT NEXT VALUE FOR dbo.seq_ContentId);
-- Now a Post might have Id=1, and a Comment Id=2 — globally unique across content types

-- ── Batch allocation — get a range of IDs at once ─────────────────────────
DECLARE @BatchStart BIGINT;
DECLARE @BatchSize  INT = 100;

EXEC sp_sequence_get_range
    @sequence_name        = N'dbo.seq_ContentId',
    @range_size           = @BatchSize,
    @range_first_value    = @BatchStart OUTPUT;

-- @BatchStart now holds the first ID; @BatchStart to @BatchStart+99 are reserved

-- ── View sequence state ───────────────────────────────────────────────────
SELECT name, current_value, start_value, increment, is_exhausted
FROM   sys.sequences
WHERE  name = 'seq_PostId';

-- ── Reset a sequence (requires ALTER SEQUENCE) ────────────────────────────
ALTER SEQUENCE dbo.seq_PostId RESTART WITH 10000;

-- ── IDENTITY vs SEQUENCE comparison ──────────────────────────────────────
-- IDENTITY:  tied to one table, cannot pre-allocate, hidden counter
-- SEQUENCE:  independent object, shareable, pre-allocable, visible/queriable
Note: The CACHE option pre-allocates a range of values in memory, reducing disk I/O for sequence generation. With CACHE 50, SQL Server reserves 50 values at startup and serves them from memory — only writing to disk every 50 values. The trade-off: if SQL Server restarts unexpectedly, up to 50 values are lost (gaps appear). This is usually acceptable. For gapless sequences (auditing requirements), use NO CACHE — but this has a performance cost because every call requires a disk write.
Tip: Use sp_sequence_get_range to pre-allocate a batch of IDs from the application layer before performing bulk inserts. Retrieving 1,000 IDs in one call is far faster than calling NEXT VALUE FOR 1,000 times individually. In C#: call ExecuteScalarAsync("SELECT NEXT VALUE FOR dbo.seq_PostId") for single IDs, or use a stored procedure wrapping sp_sequence_get_range for batch allocation. This eliminates the roundtrip cost for bulk ID generation in import workflows.
Warning: Sequences with NO CYCLE throw an error when exhausted — the sequence has reached MAXVALUE and cannot generate more values. Monitor sequence exhaustion proactively by querying sys.sequences for is_exhausted or checking when current_value approaches maximum_value. For the BlogApp with an INT sequence starting at 10000, exhaustion is theoretical (over 2 billion possible values), but for a high-volume system or a sequence with a low MAXVALUE, track this actively.

IDENTITY vs SEQUENCE Reference

Feature IDENTITY SEQUENCE
Scope One table only Database-wide, shareable
Pre-allocate IDs No — INSERT required Yes — NEXT VALUE FOR
Multiple tables No Yes — shared counter
Gap behaviour Gaps on rollback Gaps on rollback + cache loss
Reset DBCC CHECKIDENT ALTER SEQUENCE RESTART
Query current value SCOPE_IDENTITY() sys.sequences

Common Mistakes

Mistake 1 — Expecting gapless sequences with CACHE (restart causes gaps)

❌ Wrong — sequence with CACHE 50; server restart loses cached values; 50-value gap appears in IDs.

✅ Correct — use NO CACHE for gapless sequences (accepts performance cost) or document that gaps are expected.

Mistake 2 — Not monitoring sequence exhaustion (system fails with no warning)

❌ Wrong — INT sequence hits MAXVALUE; all inserts fail with “sequence object exhausted” error; no monitoring in place.

✅ Correct — query sys.sequences in monitoring jobs and alert when current_value > maximum_value * 0.8.

🧠 Test Yourself

A sequence has CACHE 50. SQL Server is restarted mid-day after allocating 30 values from the current cache batch. What happens to the remaining 20 cached values?