sql

Paginación con LIMIT/OFFSET en SQL

Cómo implementar paginación de registros usando LIMIT y OFFSET en SQL. Incluye ejemplo completo con un endpoint en Express.js.

3 min
3 jul

La forma más común de paginar datos desde una base de datos relacional.

Contexto

Cuando tienes una tabla con cientos o miles de registros, cargarlos todos de golpe es ineficiente. LIMIT controla cuántos resultados devolver y OFFSET cuántos saltar antes de empezar.

Consulta SQL con paginación

-- Página 1: registros 1 a 10
SELECT * FROM usuarios
ORDER BY id ASC
LIMIT 10 OFFSET 0;

-- Página 2: registros 11 a 20
SELECT * FROM usuarios
ORDER BY id ASC
LIMIT 10 OFFSET 10;

-- Página 3: registros 21 a 30
SELECT * FROM usuarios
ORDER BY id ASC
LIMIT 10 OFFSET 20;

La fórmula es simple: OFFSET = (page - 1) * limit.

Endpoint completo con paginación en Express.js

const express = require('express');
const mysql = require('mysql2/promise');
const app = express();

const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'tienda',
    waitForConnections: true
});

// GET /api/usuarios?page=1&limit=10
app.get('/api/usuarios', async (req, res) => {
    try {
        // Parámetros con valores por defecto
        const page = Math.max(1, parseInt(req.query.page) || 1);
        const limit = Math.min(100, Math.max(1, parseInt(req.query.limit) || 10));
        const offset = (page - 1) * limit;

        // Obtener total de registros
        const [countResult] = await pool.query(
            'SELECT COUNT(*) AS total FROM usuarios'
        );
        const total = countResult[0].total;

        // Obtener datos paginados
        const [rows] = await pool.query(
            'SELECT * FROM usuarios ORDER BY id ASC LIMIT ? OFFSET ?',
            [limit, offset]
        );

        // Respuesta con metadatos
        res.json({
            data: rows,
            pagination: {
                page,
                limit,
                total,
                totalPages: Math.ceil(total / limit)
            }
        });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

app.listen(3000, () => {
    console.log('Servidor corriendo en http://localhost:3000');
});

Explicación paso a paso

  1. Parámetros de entrada: page y limit vienen de la query string, con validación para evitar valores negativos o límites excesivos.
  2. Cálculo de OFFSET: (page - 1) * limit convierte página 1 en OFFSET 0, página 2 en OFFSET 10, etc.
  3. Consulta de total: se necesita un COUNT(*) aparte para calcular el número de páginas.
  4. Consulta paginada: LIMIT ? OFFSET ? con parámetros escapados evita inyección SQL.
  5. Metadatos: se devuelven page, limit, total y totalPages para que el frontend sepa cuántas páginas hay.

Tip extra: paginación con cursor (alternativa para datos en tiempo real)

-- En lugar de OFFSET, usas el último ID visto
SELECT * FROM usuarios
WHERE id > ?  -- último ID de la página anterior
ORDER BY id ASC
LIMIT 10;

Esta variante es más eficiente en tablas grandes y evita duplicados cuando se insertan registros nuevos entre consultas.

La paginación con LIMIT/OFFSET es simple, funciona y es el estándar: úsala mientras tu volumen de datos no exija cursores.

¡Código copiado!