OFFSET y FETCH: dividir un resultado ordenado en páginas

Una pantalla de búsqueda rara vez muestra miles de filas a la vez. Necesitamos solicitar una porción del resultado: la primera página, la segunda o cualquier bloque posterior.

OFFSET indica cuántas filas deben omitirse y FETCH cuántas se devolverán. Ambos dependen de un ORDER BY estable.

Sintaxis

SELECT Columna1, Columna2
FROM dbo.Tabla
ORDER BY ColumnaDeOrden
OFFSET FilasAOmitir ROWS
FETCH NEXT TamanoPagina ROWS ONLY;

Ejemplo: paginar productos

DECLARE @NumeroPagina INT = 2;
DECLARE @TamanoPagina INT = 2;

SELECT
    p.IdProducto,
    p.Nombre,
    p.Precio
FROM dbo.Producto AS p
WHERE p.Activo = 1
ORDER BY
    p.Precio,
    p.IdProducto
OFFSET (@NumeroPagina - 1) * @TamanoPagina ROWS
FETCH NEXT @TamanoPagina ROWS ONLY;

DECLARE crea los dos valores reutilizables; tiene una ficha específica dentro de esta referencia.

La página 2 omite las dos primeras filas y devuelve las dos siguientes. IdProducto completa el orden cuando varios productos tienen el mismo precio.

Obtener el total para la interfaz

Una función de ventana puede incluir la cantidad total antes de la paginación:

SELECT
    p.IdProducto,
    p.Nombre,
    p.Precio,
    COUNT(*) OVER () AS TotalResultados
FROM dbo.Producto AS p
WHERE p.Activo = 1
ORDER BY
    p.Precio,
    p.IdProducto
OFFSET (@NumeroPagina - 1) * @TamanoPagina ROWS
FETCH NEXT @TamanoPagina ROWS ONLY;

Cada fila de la página repetirá TotalResultados. Si la página solicitada queda fuera del rango y no devuelve filas, tampoco tendremos ese total; la aplicación debe contemplar ese caso.

Validar los parámetros

Una página menor que 1 o un tamaño negativo no representa una solicitud válida. Podemos normalizar o rechazar esos valores antes de ejecutar la consulta:

IF @NumeroPagina < 1
    SET @NumeroPagina = 1;

IF @TamanoPagina < 1 OR @TamanoPagina > 100
    SET @TamanoPagina = 25;

El límite máximo evita que la paginación se utilice accidentalmente como una descarga sin control.

Qué conviene comprobar

La paginación por desplazamiento puede volverse costosa en páginas muy lejanas, porque SQL Server todavía debe localizar y omitir las filas anteriores. Para recorridos profundos puede convenir paginar a partir de la última llave vista, técnica conocida como paginación por cursor o por clave.

También debemos considerar cambios concurrentes. Si se insertan o eliminan filas entre solicitudes, una misma fila puede cambiar de página. Un orden determinista reduce la ambigüedad, pero no convierte varias consultas en una fotografía inmutable.

Relacionado con

  • ORDER BY: es obligatorio para definir qué filas se omiten.
  • TOP: limita un resultado sin expresar directamente una página.
  • ROW_NUMBER: permite construir otra forma de paginación.
  • Índices: pueden ayudar a localizar eficientemente el orden y los filtros utilizados.