Il problema di dover paginare i risultati di una query a SQL Server (ma più in generale ad un qualunque DBMS) ricorre spesso, specialmente nelle applicazioni distribuite dove è fondamentale ridurre al minimo la quantità di dati trasportati sul canale.
SQL Server 2005 offre alcuni operatori SQL che permettono di costruire uno "schema" per tradurre una qualunque query nella equivalente versione che restituisce la sola pagina di dati voluta.
Supponiamo ad es. di voler paginare i dati della seguente query (Northwind):

SELECT *
FROM Customers
ORDER BY CustomerID ASC

Isoliamo la clausola ORDER BY dal resto della query ottenendo quindi qualcosa del tipo

SELECT *
FROM (
    SELECT
        *,
        ROW_NUMBER() OVER (ORDER BY CustomerID ASC) AS [_RowNum]

    FROM Customers
    ) AS T
WHERE [_RowNum] BETWEEN @Min AND @Max
ORDER BY [_RowNum] ASC

I colori mostrano le parti della query SQL prima e dopo la trasformazione. I parametri @Min e @Max devono essere sostituiti rispettivamente con l'indice del primo record e dell'ultimo record della pagina desiderata.
Lo schema indicato, con alcune limitazioni (ad es. è obbligatorio specificare esplicitamente la clausola ORDER BY), permette di trasformare una query complessa a piacere nell'equivalente paginabile. Detto questo, è possibile costruire una stored procedure che, facendo uso degli statement SQL dinamici, permette di paginare i risultati di una qualunque query:

CREATE PROCEDURE [dbo].[spQuery]
    @Sql NVARCHAR(MAX) = N'',
    @OrderBy NVARCHAR(MAX) = N'',
    @PageNumber INT = 1 OUT,
    @PageSize INT = 0,
    @NoRecordCount SMALLINT = 0,
    @RecordCount INT = 0 OUT
AS
    DECLARE @SqlString NVARCHAR(MAX)
    DECLARE @PageCount INT
    DECLARE @Min INT
    DECLARE @Max INT
    DECLARE @RowNum INT

    SET @Sql = LTRIM(RTRIM(ISNULL(@Sql, N'')))
    IF @Sql = N''
        RETURN 1 -- Missed Sql.

    SET @OrderBy = LTRIM(RTRIM(ISNULL(@OrderBy, N'')))
    IF @OrderBy = N''
        RETURN 2 -- Missed OrderBy.

    IF @PageNumber <= 0
        SET @PageNumber = 1

-- Vengono contati i record.
    SET @RecordCount = -1
    IF @NoRecordCount = 0
        BEGIN
            SET @SqlString = N'SELECT @RecordCount = COUNT(*) FROM (' + @Sql + N') AS T'
            EXEC sp_executesql @SqlString, N'@RecordCount int out', @RecordCount OUT
        END

-- Se occorre contare i record...
    IF @PageSize = 0
        BEGIN
            SET @PageCount = 1
            SET @PageNumber = 1
        END
    ELSE
        BEGIN
            IF @RecordCount < 0
                BEGIN
                    SET @PageCount = 0
                END
            ELSE
                BEGIN
                    SET @PageCount = CEILING(CAST(@RecordCount AS FLOAT) / CAST(@PageSize AS FLOAT))

        -- @PageNumber = 0  => ultima pagina
        -- @PageNumber = N  => N-esima pagina o ultima se @PageNumber > @PageCount
        -- @PageNumber = -N => @PageNumber = @PageCount - N (penultima, ...)
        -- Se alla fine @PageNumber < 0 => @PageNumber = @PageCount
                    IF @PageNumber <= 0
                        OR @PageNumber > @PageCount
                        SET @PageNumber = @PageCount - @PageNumber
                    IF @PageNumber <= 0
                        SET @PageNumber = @PageCount
                END
        END

    -- Deve essere rimpiazzata solo la prima occorrenza di SELECT ... => SELECT ROW_NUMBER() OVER (...) AS [_RowNum]
    IF @PageSize > 0 AND @NoRecordCount <> 0
        SET @SqlString = N'SELECT TOP ' + CAST((@PageNumber * @PageSize) AS NVARCHAR(MAX)) + N' * '
    ELSE
        SET @SqlString = N'SELECT * '

    SET @SqlString = @SqlString +
        N'FROM (' +
        N'SELECT ROW_NUMBER() OVER (ORDER BY ' + @OrderBy + N') AS [_RowNum],' + SUBSTRING(@Sql, 7, LEN(@Sql) - 6) +
        N') AS T'

    IF @PageSize > 0
        BEGIN
            SET @Min = @PageSize * ( @PageNumber - 1 ) + 1
            SET @Max = @Min + @PageSize - 1
            SET @SqlString = @SqlString + N' WHERE [_RowNum] BETWEEN @Min AND @Max'
        END

    SET @SqlString = @SqlString + N' ORDER BY [_RowNum] ASC'

    EXEC sp_executesql @SqlString, N'@Min int, @Max int', @Min = @Min, @Max = @Max

    RETURN 0 -- No error.

Esempio di invocazione:

DECLARE @RC INT

EXEC dbo.spQuery
    N'SELECT * FROM Customers',    -- Query SQL a meno della clausola ORDER BY principale
    N'CustomerID ASC',             -- Clausola ORDER BY principale
    1,                             -- Numero di pagina voluto (pagina 1 di ...)
    3,                             -- Numero di record per pagina (3 record per pagina)
    0,                             -- Mi interessa avere il conteggio dei record totali (senza paginazione)
    @RC OUT                        -- Numero di record totali