Seleccionar los N primeros

Seleccionar los N registros principales de una tabla es una operación frecuente: los 10 productos más vendidos, los 5 clientes con mayor gasto, las 3 categorías con más artículos. MySQL ofrece varias formas de hacerlo, desde el simple LIMIT hasta técnicas avanzadas con funciones de ventana para manejar empates y agrupaciones.

LIMIT básico

La forma más directa es ORDER BY combinado con LIMIT:

SELECT nombre, precio
FROM productos
ORDER BY precio DESC
LIMIT 5;
nombreprecio
ASUS ROG Zephyrus1899.99
Lenovo ThinkPad X11549.00
MacBook Air M31399.00
iPhone 15 Pro1299.99
Samsung Galaxy S24899.99

LIMIT 5 devuelve las primeras 5 filas después de aplicar el ordenamiento. Sin ORDER BY, el resultado no tiene un orden determinista y las filas seleccionadas serían impredecibles.

Los N primeros por grupo

Un escenario más interesante es obtener los N primeros de cada grupo. Por ejemplo, los 3 productos más caros de cada categoría. Aquí es donde ROW_NUMBER() resulta esencial:

SELECT nombre, categoria_nombre, precio
FROM (
    SELECT
        p.nombre,
        c.nombre AS categoria_nombre,
        p.precio,
        ROW_NUMBER() OVER (
            PARTITION BY p.categoria_id
            ORDER BY p.precio DESC
        ) AS rn
    FROM productos p
    JOIN categorias c ON p.categoria_id = c.id
) ranked
WHERE rn <= 3;
nombrecategoria_nombreprecio
iPhone 15 ProSmartphones1299.99
Samsung Galaxy S24Smartphones899.99
Google Pixel 8Smartphones699.00
ASUS ROG ZephyrusPortátiles1899.99
Lenovo ThinkPad X1Portátiles1549.00
MacBook Air M3Portátiles1399.00

PARTITION BY categoria_id reinicia la numeración para cada categoría, y ORDER BY precio DESC asegura que los más caros tengan los números más bajos. Filtrando rn <= 3, obtenemos exactamente los 3 primeros de cada grupo.

Manejar empates con RANK y DENSE_RANK

ROW_NUMBER() asigna un número único a cada fila, incluso si hay empates. Si quieres incluir todos los registros empatados en el N-ésimo puesto, usa RANK() o DENSE_RANK():

SELECT nombre, precio, ranking
FROM (
    SELECT
        nombre,
        precio,
        DENSE_RANK() OVER (ORDER BY precio DESC) AS ranking
    FROM productos
) ranked
WHERE ranking <= 3;
nombreprecioranking
ASUS ROG Zephyrus1899.991
Lenovo ThinkPad X11549.002
MacBook Air M31399.003

Si hubiera dos productos con precio 1399.00, DENSE_RANK() les asignaría a ambos el rango 3 y los incluiría en el resultado. Con ROW_NUMBER(), uno de ellos tendría rango 4 y quedaría fuera.

La diferencia entre RANK() y DENSE_RANK() es cómo manejan los saltos: RANK() salta números (1, 2, 2, 4), mientras que DENSE_RANK() no salta (1, 2, 2, 3).

Top N con porcentaje

Para seleccionar un porcentaje de las filas en lugar de un número fijo:

SET @total = (SELECT COUNT(*) FROM productos);
 
SELECT nombre, precio
FROM productos
ORDER BY precio DESC
LIMIT FLOOR(@total * 0.10);

Esto selecciona el 10% de productos con mayor precio. Nota que LIMIT no acepta expresiones directamente en versiones anteriores a MySQL 8.0, por lo que la variable es necesaria.

Los N primeros clientes por gasto

Un ejemplo práctico de los top N con agregación:

SELECT
    c.nombre,
    c.apellidos,
    SUM(p.total) AS gasto_total,
    COUNT(p.id) AS num_pedidos
FROM clientes c
JOIN pedidos p ON c.id = p.cliente_id
GROUP BY c.id, c.nombre, c.apellidos
ORDER BY gasto_total DESC
LIMIT 10;
nombreapellidosgasto_totalnum_pedidos
ElenaSánchez Ruiz15890.5023
PabloMartín Gómez12340.0018
LauraFernández Díaz11250.7515

LIMIT con OFFSET para paginación

LIMIT acepta un segundo argumento para desplazar el inicio:

-- Primeros 10
SELECT nombre, precio FROM productos ORDER BY precio DESC LIMIT 10;
 
-- Del 11 al 20
SELECT nombre, precio FROM productos ORDER BY precio DESC LIMIT 10 OFFSET 10;
 
-- Del 21 al 30
SELECT nombre, precio FROM productos ORDER BY precio DESC LIMIT 10 OFFSET 20;

Esto es la base de la paginación. Sin embargo, para offsets grandes (LIMIT 10 OFFSET 100000), el rendimiento se degrada porque MySQL debe leer y descartar las primeras 100,000 filas. Para paginación eficiente en tablas grandes, usa la técnica de cursor basada en el último valor visto.

Rendimiento

Para obtener los N primeros, un índice en la columna de ordenamiento permite a MySQL leer solo N filas del índice sin ordenar toda la tabla:

CREATE INDEX idx_precio ON productos(precio DESC);
 
-- Esta consulta usa el índice directamente, sin filesort
SELECT nombre, precio FROM productos ORDER BY precio DESC LIMIT 10;

Sin el índice, MySQL necesita leer toda la tabla y ordenarla en memoria o en disco antes de devolver las 10 primeras filas. Con el índice, lee directamente las 10 primeras entradas del índice.

Seleccionar los N primeros registros es una de las operaciones más comunes en SQL. LIMIT cubre los casos simples, y las funciones de ventana ROW_NUMBER(), RANK() y DENSE_RANK() resuelven los escenarios más avanzados con agrupaciones y empates.

Practica con los N primeros

Usa el editor para obtener los registros con valores más altos:

Simulador SQL
Ctrl+Enter para ejecutar

Escrito por Eduardo Lázaro