Blog Post

2 of my favorite new T-SQL features in SQL Server Denali (SQL 11)

,

Microsoft released the first CTP version of SQL Server Denali during the SQLPASS Community

Summit in Seattle last year. Since this major event in the SQL Server community I

had a little bit of time to look into the CTP 1 version of Denali and see what new

features they are providing us. 2 interesting features in the area of T-SQL I want

to cover today throughout this blog post: Sequences and Paging

Let’s start with sequences first. If you already worked with Oracle you should be

already familiar with sequences, because they are used everywhere in the database

design. For SQL Server sequences are a completely know concept. A sequence is nothing

else like an INT IDENTITY column, but with the difference that a sequence is created

on the database level and can be used and accessed through all your database objects

(like tables, stored procedures, stored functions, etc.). Sequences are created with

the new T-SQL statement CREATE SEQUENCE:

CREATE SEQUENCE MyNewSequence

    AS INT

    MINVALUE 1

    MAXVALUE 1000

    START WITH 1

GO

As you can see, the sequence get’s a name which must be unique within the current

database. Then you have to specify the underlying data type of the sequence, like

TINYINT, SMALLINT, INT, BIGINT. You can also specify a minimum and maximum value (MINVALUE,

MAXVALUE) and which value your sequence should return, when the sequence is accessed

for the first time (START WITH). To track all your sequences within a database, SQL

Server provides you a new system view – sys.sequences:

SELECT * FROM sys.sequences

GO

After you have created a new sequence you can start using it through the new command

NEXT VALUE FOR:

DECLARE @id INT

SET @id = NEXT VALUE FOR MyNewSequence

SELECT @id

GO

In this case SQL Server retrieves the next value from the specified sequence. In this

case you get back the value 1. If you execute the statement several times, SQL Server

returns sequentially the value 2, 3, 4, 5, 6, etc. It’s the same like a INT IDENTITY

column, but defined and used on the database level. Existing sequences can be also

altered through the ALTER SEQUENCE statement:

ALTER SEQUENCE MyNewSequence

    RESTART WITH 10

    INCREMENT BY 10

    NO MAXVALUE

GO

As you can see, you can restart the sequence on a specified value, and you have also

the possibility to specify an increment value and that you don’t want to use a maximum

value. If you specify the NO MAXVALUE option, the maximum value is defined through

the underlying data type, that is used by that sequence. Therefore it is also not

allowed to define the following sequence:

CREATE SEQUENCE InvalidSequence

    AS TINYINT

    MINVALUE 260

    MAXVALUE 300

    START WITH 260

GO

The data type TINYINT has a maximum value of 255 in SQL Server, therefore you can

define with this underlying data type a sequence for the range of 260 to 300. SQL

Server will return you an error message. You are also not allowed to define sequences

that are counting down, like:

CREATE SEQUENCE InvalidSequence

    AS TINYINT

    MINVALUE 200

    MAXVALUE 1

    START WITH 200

    INCREMENT BY -1

GO

Another nice feature of sequences is cycling, which means that the sequence returns

the first values in the sequence when it has reached the specified maximum value.

See the following example for an explanation:

CREATE SEQUENCE CyclingSequence

    AS INT

    MINVALUE 1

    MAXVALUE 10

    CYCLE

GO

DECLARE @id INT

SET @id = NEXT VALUE FOR CyclingSequence

SELECT @id

GO 15

The first statement creates a cycling sequence with a range from 1 to 10. If you execute

the next batch 15 times (GO 15), SQL Server returns 15 values from the sequence: 1,

2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 – the sequence is just cycling through its

range.

If you are working with sequences inside transactions you must be also aware that

sequences are not providing you a consistent gap-free stream of values. This is important

when your transaction is rolled back:

BEGIN TRANSACTION

DECLARE @id INT

SET @id = NEXT VALUE FOR CyclingSequence

SELECT @id

GO

ROLLBACK TRANSACTION

DECLARE @id INT

SET @id = NEXT VALUE FOR CyclingSequence

SELECT @id

GO

If you have retrieved a value from a sequence and you finally rollback your transaction,

then the value is consumed, and NEXT VALUE FOR returns the next value from your sequence.

Short story: sequences are not aware of transactions! If you want to retrieve a whole

range of sequence values, you don’t have to use a FOR loop for it. SQL Server provides

you in this case the handy system stored procedure sys.sp_sequence_get_range:

DECLARE @firstValue SQL_VARIANT, @lastValue SQL_VARIANT;

EXEC sys.sp_sequence_get_range

    @sequence_name     = 'CyclingSequence',

    @range_size        = 3,

    @range_first_value = @firstValue OUTPUT,

    @range_last_value  = @lastValue OUTPUT;

SELECT

    FirstValue = CONVERT(INT, @firstValue),

    LastVlaue = CONVERT(INT, @lastValue),

    NextValue = NEXT VALUE FOR CyclingSequence

GO

The important point to mention here is the fact, that sys.sp_sequence_get_range just

marks a range with the starting- and ending value as used. That means that you can

use the sequence values within this range without ever retrieving them explicitly.

If you mark for example the range from 5 to 10 as used, then you can use the values

5, 6, 7, 8, 9, and 10 completely at your own inside your logic. You don’t have to

retrieve them again through NEXT VALUE FOR, because the whole range was already allocated

through sys.sp_sequence_get_range. When you don’t need your sequence anymore you can

delete it from your database:

DROP SEQUENCE MyNewSequence

GO

As you can see from this explanation sequences are a very handy concept when you want

to work with unique values across your whole database. In the past I have seen a lot

of database designs where unique values where needed across different tables. That

was not possible with INT IDENTITY values, because they are scoped to a specified

table. Some people used here a centralized table which stored the current value that

was used across the database. That approach is not needed any more with sequences.

From an application development perspective sequences are also very cool, because

you can retrieve a unique value from SQL Server before ever committing your record

to the table (like with INT IDENTITY values, which are calculated only when a new

record is inserted into a table). Therefore you have now really no reason anymore

to use GUIDs as PKs (and therefore by default as clustered keys) in your database

design, which causes you A LOT of overhead and index fragmentation throughout your

whole database. Kimberly

Tripp has written in the last month a few post blogs about this specified problem,

and I recommend to to read it, and think very careful about it when you have defined

PKs on UNIQUEIDENTIFIER data types…

Another new nice feature in SQL Server Denali is NATIVE paging support, which means

you don’t have to use a workaround which is possible with the ROW_NUMBER() function

introduced with SQL Server 2005:

DECLARE

    @PageSize    TINYINT = 10,

    @CurrentPage INT     = 100;

WITH o AS

(

    SELECT TOP (@CurrentPage * @PageSize)

        [RowNumber] = ROW_NUMBER() OVER (ORDER

BY SalesOrderDetailID),

        SalesOrderDetailID,

        OrderQty,

        UnitPrice,

        LineTotal

    FROM

        Sales.SalesOrderDetail

)

SELECT

    SalesOrderDetailID,

    OrderQty,

    UnitPrice,

    LineTotal

FROM o

WHERE

    [RowNumber] BETWEEN ((@CurrentPage - 1) * @PageSize + 1)

    AND (((@CurrentPage - 1) * @PageSize) + @PageSize)

ORDER BY

    [RowNumber]

GO

When you want to use paging, you specify the record position that you want to retrieve

first from your result set. If your result set holds 1000 records, and you specify

the OFFSET of 500, SQL Server just skips the first 499 records, and starts returning

back your records at the 500th record. Finally you can specify with FETCH NEXT the

amount of records that you want to get back starting at the specified OFFSET:

DECLARE

    @PageSize    BIGINT = 10,

    @CurrentPage INT     = 100;

SELECT

    SalesOrderDetailID,

    OrderQty,

    UnitPrice,

    LineTotal

FROM Sales.SalesOrderDetail

ORDER BY SalesOrderDetailID -- The ORDER BY clause is needed by the paging mechanism!!!

OFFSET (@PageSize * (@CurrentPage - 1)) ROWS -- Specifies the number of rows to skip

before it starts to return rows from the query expression

FETCH NEXT @PageSize ROWS ONLY -- Specifies the number of rows to return after the

OFFSET clause has been processed

GO

Please be aware that the paging functionality always needs a sorted result set, therefore

you have to sort it through ORDER BY, otherwise SQL Server returns you an error message.

-Klaus

Rate

You rated this post out of 5. Change rating

Share

Share

Rate

You rated this post out of 5. Change rating