Tienes ya todas las piezas de una consulta menos una: decidir cuántas filas quieres recibir. Con las 20 filas de productos parece irrelevante, pero el día que te conectes a una tabla de producción con cincuenta millones de registros, un SELECT * FROM pedidos; sin más puede colapsar tu terminal, saturar la red y hacer que un administrador de sistemas te busque con cara de pocos amigos. LIMIT es la primera línea de defensa. Y es también la base de la paginación, uno de esos problemas que parecen resueltos en dos minutos y que en tablas grandes se convierten en un dolor de cabeza clásico. En esta lección aprenderás ambas cosas: el uso cotidiano y el uso serio.
Contenido
LIMIT: acotar el resultado- Por qué
LIMITsinORDER BYno es determinista OFFSETy la paginación clásica- Los dos problemas de
OFFSETen tablas grandes - Paginación por cursor o keyset
FETCH FIRST n ROWS ONLY: la forma estándar- Casos de uso típicos
LIMITen el orden lógico de ejecución- Errores Comunes y Consejos
- Ejercicios
- Conclusión
LIMIT: acotar el resultado
LIMIT: acotar el resultadoLIMIT n indica cuántas filas, como máximo, quieres recibir. Se escribe al final de la consulta, después de ORDER BY:
| id | nombre | precio |
|---|---|---|
| 15 | Té verde matcha ceremonial 30 g | 22.00 |
| 6 | Crema facial de aloe vera 50 ml | 18.90 |
| 20 | Cápsulas de espirulina 120 uds | 16.40 |
| 8 | Aceite corporal de almendras 200 ml | 14.25 |
| 13 | Velas de cera de soja (pack 2) | 13.75 |
Los cinco productos más caros del catálogo. Este patrón —ordenar y recortar— es la forma canónica de responder a cualquier pregunta de tipo "top N".
Comportamiento de LIMIT en los casos límite:
| Escritura | Qué devuelve |
|---|---|
LIMIT 5 |
Como mucho 5 filas |
LIMIT 100 sobre productos |
Las 20 que hay. LIMIT es un máximo, no una exigencia |
LIMIT 0 |
Ninguna fila, pero sí las cabeceras. Útil para inspeccionar los tipos de columna de una consulta sin ejecutarla del todo |
LIMIT ALL |
Todas. Equivale a no poner LIMIT; sirve para construir consultas por programa |
LIMIT NULL |
Todas, igual que LIMIT ALL |
LIMIT -1 |
ERROR: LIMIT must not be negative |
El uso más frecuente en el día a día ni siquiera lleva ORDER BY: es echar un vistazo a una tabla que no conoces.
| id | pedido_id | producto_id | cantidad | precio_unitario | descuento |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 2 | 11.95 | 0.00 |
| 2 | 1 | 2 | 3 | 3.90 | 0.00 |
| 3 | 1 | 14 | 2 | 3.25 | 0.00 |
| 4 | 2 | 6 | 1 | 17.50 | 0.00 |
| 5 | 2 | 9 | 2 | 4.60 | 0.00 |
Aquí SELECT * y la ausencia de ORDER BY son perfectamente legítimos: no quieres unas filas concretas, quieres ver qué pinta tienen los datos. Es exploración, no un informe.
Costumbre profesional: cuando te conectes a una base de datos que no conoces, escribe
LIMIT 10antes de escribir el resto de la consulta. Cuesta ocho caracteres y evita el bochorno de bloquear una sesión en producción.
- Por qué
LIMIT sin ORDER BY no es determinista
LIMIT sin ORDER BY no es deterministaRetomamos la advertencia de 02-05, porque con LIMIT se vuelve mucho más peligrosa.
| id | nombre | precio |
|---|---|---|
| 1 | Aceite de oliva virgen extra 500 ml | 12.50 |
| 2 | Arroz integral ecológico 1 kg | 3.90 |
| 3 | Miel de azahar cruda 500 g | 9.75 |
¿Qué has pedido exactamente? Tres filas cualesquiera. No las tres primeras por id, no las tres más baratas: tres filas arbitrarias, las que el motor tuviera más a mano. Que hoy salgan la 1, la 2 y la 3 es casualidad del orden físico.
La diferencia con una consulta sin LIMIT es sutil pero crucial:
| Consulta | Sin ORDER BY |
|---|---|
SELECT ... FROM productos |
Devuelve las 20 filas, en orden impredecible. El conjunto es correcto; solo el orden es incierto |
SELECT ... FROM productos LIMIT 3 |
Devuelve tres filas impredecibles. El conjunto mismo es incierto |
En el primer caso, ordenar el resultado en tu aplicación arregla el problema. En el segundo no hay arreglo posible: has perdido 17 filas y no sabes cuáles.
Regla:
LIMITsinORDER BYsolo es aceptable para explorar. En cuanto el resultado se use para algo, elORDER BYes obligatorio, y debe ser determinista (terminado en una columna única, como viste en 02-05).
OFFSET y la paginación clásica
OFFSET y la paginación clásicaOFFSET m descarta las primeras m filas del resultado antes de aplicar LIMIT. Combinados, permiten recorrer un resultado grande en trozos:
Veámoslo con el catálogo de TiendaVerde ordenado por precio descendente y páginas de cinco productos.
Página 1 — LIMIT 5 OFFSET 0:
| id | nombre | precio |
|---|---|---|
| 15 | Té verde matcha ceremonial 30 g | 22.00 |
| 6 | Crema facial de aloe vera 50 ml | 18.90 |
| 20 | Cápsulas de espirulina 120 uds | 16.40 |
| 8 | Aceite corporal de almendras 200 ml | 14.25 |
| 13 | Velas de cera de soja (pack 2) | 13.75 |
Página 2 — LIMIT 5 OFFSET 5:
| id | nombre | precio |
|---|---|---|
| 1 | Aceite de oliva virgen extra 500 ml | 12.50 |
| 10 | Detergente ecológico concentrado 1 L | 11.20 |
| 12 | Bolsas reutilizables de algodón (pack 5) | 9.90 |
| 3 | Miel de azahar cruda 500 g | 9.75 |
| 7 | Champú sólido de romero 80 g | 8.40 |
Página 3 — LIMIT 5 OFFSET 10:
| id | nombre | precio |
|---|---|---|
| 19 | Desodorante natural en barra 50 g | 7.80 |
| 11 | Estropajo vegetal de luffa (pack 3) | 5.50 |
| 17 | Zumo de naranja prensado en frío 1 L | 5.40 |
| 16 | Kombucha de jengibre 750 ml | 4.95 |
| 9 | Bálsamo labial de caléndula 15 ml | 4.60 |
Y la cuarta y última cerraría con los productos 2, 18, 14, 4 y 5.
Resumen del cálculo:
| Página | OFFSET |
LIMIT |
Productos devueltos |
|---|---|---|---|
| 1 | 0 | 5 | 15, 6, 20, 8, 13 |
| 2 | 5 | 5 | 1, 10, 12, 3, 7 |
| 3 | 10 | 5 | 19, 11, 17, 16, 9 |
| 4 | 15 | 5 | 2, 18, 14, 4, 5 |
Detalles importantes:
- El
ORDER BYes obligatorio. Sin él, cada página se calcula sobre un orden distinto y podrías ver el mismo producto en dos páginas y otro en ninguna. - El
ORDER BYdebe ser determinista. Por eso hemos escritoORDER BY precio DESC, id: si dos productos costaran lo mismo, el desempate poridgarantiza que siempre caigan en la misma página. OFFSETsinLIMITes válido:OFFSET 15devuelve las filas de la 16 en adelante.OFFSETmayor que el total devuelve cero filas, sin error.OFFSET 100sobreproductosno da nada.
- Los dos problemas de
OFFSET en tablas grandes
OFFSET en tablas grandesEsta paginación funciona, es la que aprende todo el mundo, y tiene dos defectos serios que solo se manifiestan cuando los datos crecen.
4.1. El coste crece con el número de página
OFFSET m no salta las primeras m filas: las calcula y las descarta. El motor no tiene forma de adivinar cuál es la fila número 500 001 sin haber producido las 500 000 anteriores.
| Página | Filas que el motor produce | Filas que te entrega |
|---|---|---|
| 1 | 20 | 20 |
| 50 | 1 000 | 20 |
| 500 | 10 000 | 20 |
| 50 000 | 1 000 000 | 20 |
La última página de un listado grande puede tardar cientos de veces más que la primera. Es un patrón conocido: los usuarios se quejan de que "la web va lenta al final del catálogo", y la causa está aquí. Con EXPLAIN ANALYZE (módulo 8) se ve con toda claridad en el nodo Limit, que informa de cuántas filas ha descartado.
4.2. Los datos se mueven entre página y página
Este es peor, porque no es lento: es incorrecto.
Imagina que un usuario está viendo la página 1 del catálogo ordenado por precio descendente y, en ese instante, alguien da de alta un producto de 25 € (más caro que el matcha). Cuando el usuario pulse "siguiente":
| Momento | Qué pasa |
|---|---|
| Página 1 (antes) | 15, 6, 20, 8, 13 |
| Se inserta un producto nuevo de 25.00 € | Todo el listado se desplaza una posición |
Página 2 (OFFSET 5) |
Ahora la posición 6 la ocupa el producto 13, que ya estaba en la página 1 |
El usuario ve el producto 13 dos veces. Y con un borrado ocurre lo contrario: una fila desaparece sin haberse mostrado nunca.
| Cambio en los datos | Síntoma para el usuario |
|---|---|
| Se inserta una fila que va antes de la página actual | Ve una fila repetida |
| Se borra una fila anterior a la página actual | Una fila se salta y no la ve nunca |
Cada página es una consulta independiente, ejecutada en un momento distinto y sobre un estado distinto de la tabla. OFFSET no tiene memoria de lo que ya te enseñó.
¿Cuándo se puede vivir con OFFSET?
| Escenario | ¿OFFSET es aceptable? |
|---|---|
| Panel interno con pocos miles de filas | Sí, sin problema |
| Datos que no cambian durante la sesión (un informe histórico) | Sí |
| Necesitas "ir a la página 47" directamente | Sí: es lo único que lo permite |
| Catálogo público con millones de filas y escrituras constantes | No |
| Un scroll infinito en una aplicación móvil | No |
| Exportar una tabla entera en trozos | No: usa keyset |
- Paginación por cursor o keyset
La alternativa consiste en dejar de decir "sáltate 500 000 filas" y empezar a decir "dame lo que viene después de esto". En lugar de una posición, se recuerda el último valor visto.
Volvamos a nuestro catálogo. La página 1 terminaba en el producto 13, con precio 13.75 €. La página 2 se pide así:
| id | nombre | precio |
|---|---|---|
| 1 | Aceite de oliva virgen extra 500 ml | 12.50 |
| 10 | Detergente ecológico concentrado 1 L | 11.20 |
| 12 | Bolsas reutilizables de algodón (pack 5) | 9.90 |
| 3 | Miel de azahar cruda 500 g | 9.75 |
| 7 | Champú sólido de romero 80 g | 8.40 |
Idéntica a la página 2 de la sección 3, pero obtenida sin descartar nada: el motor va directo a las filas que cumplen precio < 13.75.
Qué pasa si hay empates. Si dos productos costaran exactamente 13.75 €, WHERE precio < 13.75 se saltaría el segundo. La solución es comparar la tupla completa de las columnas de ordenación, algo que SQL permite:
SELECT id, nombre, precio
FROM productos
WHERE (precio, id) < (13.75, 13)
ORDER BY precio DESC, id DESC
LIMIT 5;La comparación (precio, id) < (13.75, 13) es lexicográfica: se cumple si precio < 13.75, o si precio = 13.75 y además id < 13. Es exactamente la condición "estrictamente después del último elemento visto" para ese orden. Ojo con un detalle: para que la comparación de tuplas encaje, todas las columnas del ORDER BY deben ir en el mismo sentido, por eso aquí aparece id DESC.
Comparativa de los dos enfoques:
| Aspecto | OFFSET |
Keyset |
|---|---|---|
| Coste de la página N | Crece linealmente con N | Constante |
| ¿Aprovecha un índice? | Solo para ordenar | Sí, para posicionarse directamente |
| Filas repetidas o saltadas si los datos cambian | Sí | No |
| ¿Permite saltar a la página 47? | Sí | No: solo "siguiente" y "anterior" |
| ¿Permite mostrar "página 3 de 120"? | Sí | No sin una consulta adicional |
| Complejidad de implementación | Trivial | Media: hay que arrastrar el cursor |
En la práctica: si tu interfaz es un scroll infinito o un botón "cargar más", el keyset es siempre la respuesta correcta. Si necesitas numeritos de página clicables, OFFSET es lo único viable y hay que asumir sus límites (o limitar el número de páginas navegables, que es lo que hacen casi todos los buscadores).
Para que el keyset rinda hace falta un índice sobre las columnas de ordenación, en este caso (precio, id). Sin él, PostgreSQL tiene que ordenar toda la tabla igualmente y no ganas nada. Los índices son el módulo 8.
FETCH FIRST n ROWS ONLY: la forma estándar
FETCH FIRST n ROWS ONLY: la forma estándarLIMIT es cómodo, universalmente conocido... y no es SQL estándar. Lo introdujo MySQL, lo adoptó PostgreSQL y hoy lo entienden casi todos los motores, pero la sintaxis oficial del estándar SQL:2008 es otra:
SELECT id, nombre, precio
FROM productos
ORDER BY precio DESC, id
OFFSET 0 ROWS
FETCH FIRST 5 ROWS ONLY;| id | nombre | precio |
|---|---|---|
| 15 | Té verde matcha ceremonial 30 g | 22.00 |
| 6 | Crema facial de aloe vera 50 ml | 18.90 |
| 20 | Cápsulas de espirulina 120 uds | 16.40 |
| 8 | Aceite corporal de almendras 200 ml | 14.25 |
| 13 | Velas de cera de soja (pack 2) | 13.75 |
Exactamente el mismo resultado. PostgreSQL admite ambas formas y las trata igual.
PostgreSQL añade además WITH TIES, que amplía el resultado para incluir todas las filas empatadas con la última:
Sobre estos datos devuelve las mismas 5 filas, porque no hay dos productos con el mismo precio. Pero si hubiera tres productos a 13.75 €, WITH TIES devolvería 7 filas en lugar de 5: es la forma correcta de hacer un "top 5" honesto en una competición. WITH TIES exige ORDER BY y no funciona con LIMIT, solo con FETCH.
Tabla comparativa entre motores:
| Motor | Sintaxis principal | Alternativas |
|---|---|---|
| Estándar SQL:2008 | OFFSET m ROWS FETCH FIRST n ROWS ONLY |
— |
| PostgreSQL | LIMIT n OFFSET m |
FETCH FIRST n ROWS ONLY, WITH TIES |
| MySQL / MariaDB | LIMIT n OFFSET m |
LIMIT m, n (¡los argumentos al revés!) |
| SQLite | LIMIT n OFFSET m |
LIMIT m, n |
| SQL Server 2012+ | OFFSET m ROWS FETCH NEXT n ROWS ONLY |
SELECT TOP (n) ... (sin offset) |
| Oracle 12c+ | OFFSET m ROWS FETCH FIRST n ROWS ONLY |
— |
| Oracle 11g y anteriores | WHERE ROWNUM <= n sobre una subconsulta ordenada |
— |
Dos trampas de esta tabla que conviene subrayar:
LIMIT m, nde MySQL invierte el significado: el primer número es el desplazamiento y el segundo la cantidad.LIMIT 5, 10en MySQL esLIMIT 10 OFFSET 5en PostgreSQL. Una consulta copiada sin leerla devuelve datos equivocados.ROWNUMde Oracle se asigna antes delORDER BY.WHERE ROWNUM <= 5 ORDER BY precio DESCdevuelve cinco filas cualesquiera y luego las ordena: no es el top 5. Hay que ordenar en una subconsulta y aplicarROWNUMfuera.
¿Cuál usar? En este curso, LIMIT: es lo que verás en el 99 % del código PostgreSQL. Si escribes SQL que deba ejecutarse en varios motores, OFFSET ... FETCH FIRST ... ROWS ONLY es la apuesta segura.
- Casos de uso típicos
7.1. Top N
Ya lo has visto con los productos más caros. Otra variante, los cinco de mayor margen:
SELECT nombre,
precio,
coste,
precio - coste AS margen
FROM productos
WHERE activo
ORDER BY margen DESC, id
LIMIT 5;| nombre | precio | coste | margen |
|---|---|---|---|
| Té verde matcha ceremonial 30 g | 22.00 | 12.50 | 9.50 |
| Crema facial de aloe vera 50 ml | 18.90 | 9.50 | 9.40 |
| Aceite corporal de almendras 200 ml | 14.25 | 7.10 | 7.15 |
| Velas de cera de soja (pack 2) | 13.75 | 6.90 | 6.85 |
| Bolsas reutilizables de algodón (pack 5) | 9.90 | 4.30 | 5.60 |
Fíjate en que las Cápsulas de espirulina (margen 7.70 €, tercera del catálogo) no aparecen: el WHERE activo las descarta antes de ordenar. El orden lógico manda: primero se filtra, después se ordena, y solo al final se recorta.
7.2. Los últimos registros
SELECT id, cliente_id, fecha_pedido, estado, metodo_pago
FROM pedidos
ORDER BY fecha_pedido DESC, id DESC
LIMIT 3;| id | cliente_id | fecha_pedido | estado | metodo_pago |
|---|---|---|---|---|
| 20 | 9 | 2026-02-21 | pendiente | contrareembolso |
| 19 | 6 | 2026-02-09 | pagado | tarjeta |
| 18 | 5 | 2026-01-27 | pagado | transferencia |
Los tres últimos pedidos recibidos: el contenido natural de un panel de "actividad reciente".
7.3. El registro extremo
| id | nombre | apellidos | fecha_registro |
|---|---|---|---|
| 1 | Lucía | Martínez Soler | 2025-01-10 |
La primera clienta de TiendaVerde. Este patrón —ORDER BY ... LIMIT 1— es equivalente a usar MIN/MAX, pero con una ventaja: te devuelve la fila entera, no solo el valor mínimo. Con MIN(fecha_registro) sabrías la fecha, pero no quién es. Las funciones de agregación llegan en la lección 04-04.
7.4. Muestreo rápido para exploración
| id | producto_id | cliente_id | puntuacion | comentario | fecha |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 5 | Aceite excelente, sabor intenso y envase muy cuidado. | 2025-03-15 |
| 2 | 2 | 1 | 4 | Buen arroz, aunque tarda algo más en cocer de lo habitual. | 2025-03-16 |
| 3 | 6 | 2 | 5 | La crema deja la piel muy suave. Repetiré sin duda. | 2025-03-25 |
LIMIT en el orden lógico de ejecución
LIMIT en el orden lógico de ejecuciónCon LIMIT se completa el ciclo que empezamos en 02-01. Este es el diagrama definitivo del módulo:
flowchart LR
A["1 · FROM<br/>origen de las filas"] --> B["2 · WHERE<br/>filtra filas"]
B --> C["3 · SELECT<br/>proyecta y calcula<br/>nacen los alias"]
C --> D["3b · DISTINCT<br/>elimina duplicados"]
D --> E["4 · ORDER BY<br/>ordena el resultado"]
E --> F["5 · LIMIT / OFFSET<br/>recorta"]
LIMIT es siempre lo último. De ahí salen consecuencias que conviene tener claras:
| Pregunta | Respuesta |
|---|---|
¿LIMIT 5 hace que WHERE examine solo 5 filas? |
No. WHERE se aplica a todas las filas de la tabla |
¿LIMIT 5 con ORDER BY ordena solo 5 filas? |
No. Lógicamente se ordena todo y luego se recorta |
Entonces, ¿LIMIT no ahorra trabajo? |
Sí que lo ahorra, pero en el plan físico, no en el lógico |
Ese último punto merece un matiz. Aunque lógicamente se ordene todo, PostgreSQL es más listo que eso: cuando ve un ORDER BY ... LIMIT n pequeño, usa un algoritmo llamado top-N heapsort, que mantiene en memoria solo las n mejores filas vistas hasta el momento en lugar de ordenar el conjunto entero. Y si existe un índice que ya devuelve las filas en el orden pedido, puede leer las n primeras y parar, sin tocar el resto de la tabla.
Es el ejemplo perfecto de la distinción de 02-01: el orden lógico define el significado de la consulta, y el plan físico que elige el optimizador puede ser radicalmente más eficiente mientras produzca el mismo resultado. Lo verás con tus propios ojos en el módulo 8 con EXPLAIN.
Errores Comunes y Consejos
LIMITsinORDER BYen cualquier consulta que no sea exploratoria. Devuelve filas arbitrarias y el problema no se puede arreglar después.ORDER BYno determinista al paginar. Si hay empates y no los desempatas con una columna única, una misma fila puede aparecer en dos páginas y otra en ninguna.- Copiar
LIMIT 10, 20de un ejemplo de MySQL. En MySQL significa "20 filas desde la 11"; en PostgreSQL ni siquiera es sintaxis válida. - Creer que
OFFSETsalta filas sin coste. Las produce y las tira. En la página 5 000 lo notarás. - Paginar con
OFFSETsobre datos que cambian. Filas repetidas y filas saltadas, sin ningún error que lo delate. - Aplicar el keyset con un
ORDER BYque no coincide con la condición delWHERE. Las columnas deben ser las mismas, en el mismo orden y en el mismo sentido. - Usar
LIMITpara "arreglar" una consulta lenta. Si la consulta es lenta por falta de índice,LIMITpuede seguir siendo lento: el motor tiene que encontrar las filas antes de recortarlas. - Esperar que
LIMIT 1sustituya a unWHEREbien escrito. Devolver una fila cualquiera de las que cumplen la condición rara vez es lo que se quiere. - Consejo:
LIMITprimero, consulta después. Al explorar una base desconocida, escríbelo antes de nada. - Consejo:
LIMIT 0es útil. Te da las columnas y sus tipos sin traer datos, ideal para comprobar la forma de una consulta compleja. - Consejo: para un "top N" honesto, plantéate
FETCH FIRST n ROWS WITH TIES. Cortar por el número 5 exacto cuando el 5 y el 6 empatan es engañoso.
Ejercicios
Ejercicio 1
Atención al cliente necesita un panel con los cinco pedidos más antiguos que todavía no están entregados ni cancelados, mostrando id, cliente_id, fecha_pedido, estado y gastos_envio. Escribe la consulta y explica cuántas filas devuelve realmente y por qué.
Ejercicio 2
Construye la segunda página de un listado de clientes ordenado por país y, dentro de cada país, por apellidos, con páginas de cuatro clientes. Escríbela primero con OFFSET y después con paginación keyset. Explica qué información necesita la aplicación en cada caso para pedir la página siguiente.
Ejercicio 3
El equipo de compras quiere las tres líneas de pedido de mayor importe. Escribe la consulta usando la sintaxis estándar FETCH FIRST, e indica qué cambiaría si usaras WITH TIES. Después responde: ¿por qué esta consulta no puede decirte de qué producto se trata?
Soluciones
Solución 1
SELECT id,
cliente_id,
fecha_pedido,
estado,
gastos_envio
FROM pedidos
WHERE estado <> 'entregado'
AND estado <> 'cancelado'
ORDER BY fecha_pedido, id
LIMIT 5;| id | cliente_id | fecha_pedido | estado | gastos_envio |
|---|---|---|---|---|
| 16 | 4 | 2025-12-19 | enviado | 4.95 |
| 17 | 7 | 2026-01-13 | enviado | 9.90 |
| 18 | 5 | 2026-01-27 | pagado | 4.95 |
| 19 | 6 | 2026-02-09 | pagado | 4.95 |
| 20 | 9 | 2026-02-21 | pendiente | 12.50 |
5 filas, que resultan ser todas las que cumplen la condición: de los 20 pedidos, 14 están entregados y 1 cancelado, así que solo quedan 5 en curso. El LIMIT 5 no ha recortado nada, y ese es un comportamiento normal: LIMIT es un máximo, no una promesa.
El razonamiento por el orden lógico: FROM trae 20 filas → WHERE deja 5 → SELECT proyecta → ORDER BY fecha_pedido, id las ordena de más antigua a más reciente → LIMIT 5 no descarta ninguna. Si mañana entran diez pedidos nuevos, la consulta seguirá devolviendo los cinco más antiguos sin tocar una coma.
En el módulo 4 escribirás esa condición de forma más limpia como WHERE estado NOT IN ('entregado', 'cancelado').
Solución 2
Con OFFSET, la página 2 (clientes 5 a 8 del listado):
SELECT id, nombre, apellidos, ciudad, pais
FROM clientes
ORDER BY pais, apellidos, id
LIMIT 4 OFFSET 4;El listado completo ordenado por pais, apellidos empieza así: Belmonte Roca (5), Bosch Ferrer (13), Carrasco Vega (15), Ferrer Ibáñez (2) —página 1—, y continúa:
| id | nombre | apellidos | ciudad | pais |
|---|---|---|---|---|
| 14 | Hugo | Iglesias Pardo | Zaragoza | España |
| 6 | Pau | Llorens Vidal | Valencia | España |
| 1 | Lucía | Martínez Soler | Valencia | España |
| 11 | Elena | Navarro Puig | Alicante | España |
Con paginación keyset, partiendo del último elemento de la página 1 —Carlos Ferrer Ibáñez, país España, apellidos Ferrer Ibáñez, id 2—:
SELECT id, nombre, apellidos, ciudad, pais
FROM clientes
WHERE (pais, apellidos, id) > ('España', 'Ferrer Ibáñez', 2)
ORDER BY pais, apellidos, id
LIMIT 4;Devuelve exactamente las mismas cuatro filas.
Qué necesita la aplicación en cada caso:
| Enfoque | Qué arrastra entre peticiones |
|---|---|
OFFSET |
Solo un número: la página actual. Fácil de poner en una URL (?page=2) y permite saltar a cualquier página |
| Keyset | Los valores de ordenación de la última fila mostrada (aquí pais, apellidos e id). Se guardan en un "cursor" que la aplicación devuelve al pedir la siguiente página |
Fíjate en dos detalles del keyset: la tupla del WHERE contiene exactamente las mismas columnas del ORDER BY y en el mismo orden, y se incluye id como última columna precisamente para desempatar dos clientes que compartieran país y apellidos. Sin ese id, un apellido repetido haría que se perdiera un cliente entre página y página.
Solución 3
SELECT id,
pedido_id,
cantidad,
precio_unitario,
descuento,
ROUND(cantidad * precio_unitario * (1 - descuento), 2) AS importe
FROM lineas_pedido
ORDER BY cantidad * precio_unitario * (1 - descuento) DESC, id
FETCH FIRST 3 ROWS ONLY;| id | pedido_id | cantidad | precio_unitario | descuento | importe |
|---|---|---|---|---|---|
| 28 | 12 | 2 | 22.00 | 0.00 | 44.00 |
| 18 | 8 | 3 | 12.50 | 0.05 | 35.63 |
| 24 | 10 | 2 | 18.90 | 0.10 | 34.02 |
Qué cambiaría con WITH TIES. Nada, en este caso: la cuarta línea vale 26.73 €, muy lejos de los 34.02 € de la tercera, así que no hay empate que arrastrar. Pero conviene notar que WITH TIES no puede llevar un desempate artificial en el ORDER BY: si dejas el , id final, cada fila tiene un valor de ordenación único y nunca habrá empates que incluir. Para que WITH TIES sirva de algo hay que ordenar solo por el criterio real:
Con estos datos devuelve las mismas 3 filas. Si hubiera dos líneas empatadas en 34.02 €, devolvería 4.
Por qué no puedes saber de qué producto se trata. Porque lineas_pedido guarda producto_id, un número, y el nombre del producto vive en la tabla productos. Con las herramientas de este módulo solo puedes consultar una tabla a la vez: lo máximo que puedes decir es que la línea 28 corresponde al producto 15. Para escribir "Té verde matcha ceremonial 30 g" en el informe hace falta combinar ambas tablas, y eso es precisamente lo que empieza en la lección siguiente.
Conclusión
Has cerrado el ciclo completo de una consulta:
LIMIT nacota el resultado a un máximo denfilas y es la primera línea de defensa al explorar tablas grandes o desconocidas.LIMITsinORDER BYno es determinista: devuelve filas arbitrarias, y ese daño no se puede reparar después.OFFSET mdescarta las primerasmfilas y permite la paginación clásicaLIMIT tamaño OFFSET (N-1)*tamaño, que exige unORDER BYdeterminista.- Esa paginación tiene dos defectos en tablas grandes: el coste crece con el número de página, y las inserciones o borrados hacen que se repitan o se salten filas.
- La paginación por cursor o keyset —
WHERE (columnas) < (últimos valores vistos)— tiene coste constante y no se descuadra, a cambio de no poder saltar a una página arbitraria. FETCH FIRST n ROWS ONLYes la forma estándar, yWITH TIESamplía el resultado con las filas empatadas. Cada motor tiene su dialecto, yLIMIT m, nde MySQL invierte los argumentos.LIMITes el paso 5 y último del orden lógico; el optimizador, sin embargo, sí aprovecha su presencia con estrategias como el top-N heapsort.
Y con esto cierras el módulo 2 completo. Sabes construir una consulta de principio a fin: elegir su origen con FROM, filtrar filas con WHERE, proyectar y calcular columnas con SELECT y sus alias, eliminar repeticiones con DISTINCT, imponer un orden con ORDER BY y recortar el resultado con LIMIT. Y sabes en qué orden lógico ocurre todo eso, que es lo que te ha permitido entender por qué un alias funciona en un sitio y no en otro.
Pero fíjate en el techo con el que has chocado una y otra vez: pedidos te dice cliente_id = 9 y no "Camille Dubois"; lineas_pedido te dice producto_id = 15 y no "Té verde matcha ceremonial"; productos te dice categoria_id = 2 y no "Cosmética natural". Todas tus consultas han mirado una sola tabla, y las preguntas que de verdad le interesan a TiendaVerde —qué compró cada cliente, qué categoría factura más, qué comercial cierra más pedidos, qué productos no ha comprado nadie— viven precisamente en las relaciones entre tablas que dibujaste en el diagrama de 01-06. En el módulo 3, Consultas con múltiples tablas, aprenderás a recorrer esas relaciones con JOIN: el INNER JOIN para lo que casa a ambos lados, el LEFT y el RIGHT para conservar lo que no casa —ahí aparecerán por fin los tres clientes sin pedidos y los tres productos nunca vendidos—, el FULL OUTER para ambas cosas a la vez, el SELF JOIN para las relaciones reflexivas de empleados y clientes, y UNION, INTERSECT y EXCEPT para combinar resultados enteros. Las nueve tablas de TiendaVerde dejan de ser nueve islas.
Curso de SQL
Módulo 1: Introducción a SQL
- ¿Qué es SQL?
- Configurando tu entorno SQL
- Sintaxis básica de SQL
- Entendiendo bases de datos y tablas
- El modelo relacional: claves primarias y foráneas
- La base de datos del curso: TiendaVerde
Módulo 2: Consultas básicas de SQL
- Instrucción SELECT
- Alias, expresiones y columnas calculadas
- Filtrando datos con WHERE
- DISTINCT y eliminación de duplicados
- Ordenando datos con ORDER BY
- Limitando resultados con LIMIT
Módulo 3: Trabajando con múltiples tablas
- Operaciones JOIN
- INNER JOIN
- LEFT JOIN
- RIGHT JOIN
- FULL OUTER JOIN
- SELF JOIN y CROSS JOIN
- Uniones de conjuntos: UNION, INTERSECT y EXCEPT
Módulo 4: Filtrado avanzado de datos
- Usando LIKE para coincidencia de patrones
- Operadores IN y BETWEEN
- Valores NULL y IS NULL
- Funciones de agregación: COUNT, SUM, AVG, MIN y MAX
- Agregando datos con GROUP BY
- Cláusula HAVING
Módulo 5: Manipulación de datos
- Creando tablas y restricciones con CREATE TABLE
- Instrucción INSERT
- Instrucción UPDATE
- Instrucción DELETE
- Instrucción UPSERT (MERGE)
- Modificando el esquema: ALTER TABLE y migraciones seguras
Módulo 6: Funciones avanzadas de SQL
- Funciones de cadena
- Funciones numéricas
- Funciones de fecha y hora
- Conversión de tipos y manejo de NULL: CAST y COALESCE
- Expresiones condicionales
Módulo 7: Subconsultas y consultas anidadas
- Introducción a subconsultas
- Subconsultas correlacionadas
- EXISTS y NOT EXISTS
- Usando subconsultas en cláusulas SELECT, FROM y WHERE
- Subconsultas o JOIN: cuál elegir
Módulo 8: Índices y optimización de rendimiento
- Entendiendo los índices
- Creación y gestión de índices
- Tipos de índice y cuándo no indexar
- Técnicas de optimización de consultas
- Análisis del rendimiento de consultas
Módulo 9: Transacciones y concurrencia
- Introducción a las transacciones
- Propiedades ACID
- Instrucciones de control de transacciones
- Niveles de aislamiento y anomalías de concurrencia
- Manejo de concurrencia: bloqueos e interbloqueos
Módulo 10: Temas avanzados
- Vistas
- Expresiones de tabla comunes (CTE)
- Funciones de ventana
- Procedimientos almacenados
- Triggers
- JSON y datos semiestructurados
Módulo 11: SQL en la práctica
- Casos de uso en el mundo real
- Mejores prácticas
- Seguridad: inyección SQL, permisos y roles
- SQL para análisis de datos
- SQL en desarrollo web
