SQL · Guías · 30 de octubre de 2026 · 14 min de lectura
ORDER BY en SQL: ordenar ASC y DESC, por varias columnas, NULLS LAST y top N

ORDER BY ordena las filas del resultado de una consulta. Se escribe al final, con la lista de columnas por las que ordenar, y detrás de cada una ASC (ascendente, la opción por defecto) o DESC (descendente): ORDER BY range DESC pone primero los aviones de mayor autonomía. Con varias columnas, la segunda solo desempata la primera. Sin ORDER BY, la base de datos devuelve las filas en un orden que no está garantizado, así que cualquier consulta en la que el orden importe, y toda consulta con LIMIT, necesita uno. Los dos detalles que más fallos provocan: dónde van los NULL (cambia según la base de datos, así que escribe NULLS FIRST o NULLS LAST) y los empates en el corte de un top N.
Todos los ejemplos usan la base de datos de una aerolínea del curso de SQL de Nomto, con sintaxis de PostgreSQL sobre DuckDB (el motor de la sala de SQL de Nomto). Los valores están en inglés y cada tabla de resultados es la salida real de la consulta.
Sintaxis de ORDER BY
SELECT columns
FROM table
WHERE condition
ORDER BY column1 [ASC | DESC] [NULLS FIRST | NULLS LAST],
column2 [ASC | DESC] ...
LIMIT n;
ORDER BY va después de WHERE, GROUP BY y HAVING, y antes de LIMIT. Además es uno de los últimos pasos que ejecuta la base de datos, y por eso puede usar los alias definidos en SELECT (lo vemos más abajo).
Orden ascendente y descendente
Los nueve modelos de avión, ordenados por autonomía de menor a mayor:
SELECT model, range
FROM aircrafts
ORDER BY range;
| model | range |
|---|---|
| Cessna 208 Caravan | 1200 |
| Bombardier CRJ-200 | 2700 |
| Sukhoi Superjet-100 | 3000 |
| Boeing 737-300 | 4200 |
| Airbus A321-200 | 5600 |
| Airbus A320-200 | 5700 |
| Airbus A319-100 | 6700 |
| Boeing 767-300 | 7900 |
| Boeing 777-300 | 11100 |
ORDER BY range es lo mismo que ORDER BY range ASC. Para empezar por el de mayor autonomía, escribe ORDER BY range DESC. Los números se ordenan por valor, las fechas y marcas de tiempo de la más antigua a la más reciente, y el texto alfabéticamente (con matices que vemos en la sección sobre ordenar texto).
ORDER BY por varias columnas
Pon varias columnas separadas por comas. Las filas se ordenan por la primera; la segunda solo se usa cuando la primera es igual, y así sucesivamente. Cada columna lleva su propia dirección. Aviones agrupados por fabricante de la A a la Z y, dentro de cada fabricante, por autonomía de mayor a menor:
SELECT split_part(model, ' ', 1) AS maker, model, range
FROM aircrafts
ORDER BY maker, range DESC;
| maker | model | range |
|---|---|---|
| Airbus | Airbus A319-100 | 6700 |
| Airbus | Airbus A320-200 | 5700 |
| Airbus | Airbus A321-200 | 5600 |
| Boeing | Boeing 777-300 | 11100 |
| Boeing | Boeing 767-300 | 7900 |
| Boeing | Boeing 737-300 | 4200 |
| Bombardier | Bombardier CRJ-200 | 2700 |
| Cessna | Cessna 208 Caravan | 1200 |
| Sukhoi | Sukhoi Superjet-100 | 3000 |
Un error frecuente es escribir ORDER BY maker, range DESC y esperar que las dos columnas vayan en orden descendente. DESC se aplica solo a la columna que tiene justo delante; aquí maker sigue siendo ascendente.
Ordenar por alias, por posición o por una expresión
ORDER BY puede referirse a:
- un alias de
SELECT:makerno es una columna real, es el alias de una expresión; - una expresión:
ORDER BY split_part(model, ' ', 1)da el mismo resultado; - un número de columna:
ORDER BY 2 DESCordena por la segunda columna de la lista delSELECT; - una columna que ni siquiera se selecciona:
SELECT city FROM airports ORDER BY timezonees válido.
Ordenar un resultado agrupado por su agregado, por alias o por posición:
SELECT status, COUNT(*) AS flights
FROM flights
GROUP BY status
ORDER BY flights DESC; -- or ORDER BY 2 DESC, or ORDER BY COUNT(*) DESC
| status | flights |
|---|---|
| Arrived | 16707 |
| Scheduled | 15383 |
| On Time | 518 |
| Cancelled | 414 |
| Departed | 58 |
| Delayed | 41 |
Las posiciones (ORDER BY 2) son cómodas mientras exploras, pero frágiles en consultas guardadas: añades una columna al principio del SELECT y el orden cambia sin avisar. En cualquier cosa que vaya a leer otra persona, usa nombres.
Dos restricciones que conviene conocer. Un alias se puede usar solo, pero no siempre dentro de una expresión: PostgreSQL rechaza ORDER BY flights * 2 cuando flights es un alias. Y con SELECT DISTINCT, PostgreSQL exige que todas las expresiones de ORDER BY estén en la lista del SELECT (“for SELECT DISTINCT, ORDER BY expressions must appear in select list”); DuckDB es más permisivo, pero no cuentes con ello.
Orden personalizado con CASE
El orden alfabético de los estados no dice nada; lo que se quiere ver es el ciclo de vida del vuelo. Asigna un número a cada valor con CASE y ordena por él:
SELECT status, COUNT(*) AS flights
FROM flights
GROUP BY status
ORDER BY CASE status
WHEN 'Scheduled' THEN 1
WHEN 'On Time' THEN 2
WHEN 'Delayed' THEN 3
WHEN 'Departed' THEN 4
WHEN 'Arrived' THEN 5
ELSE 6
END;
| status | flights |
|---|---|
| Scheduled | 15383 |
| On Time | 518 |
| Delayed | 41 |
| Departed | 58 |
| Arrived | 16707 |
| Cancelled | 414 |
El ELSE manda al final cualquier valor inesperado en lugar de ponerlo arriba. El mismo truco fija un valor en primera posición: ORDER BY city = 'Moscow' DESC, city muestra primero Moscú y luego el resto por orden alfabético (en PostgreSQL y DuckDB true va después de false, de ahí el DESC). MySQL tiene además ORDER BY FIELD(status, 'Scheduled', 'On Time', ...), que no existe en otras bases de datos. Más patrones de CASE en la guía de CASE WHEN en SQL.
NULLS FIRST y NULLS LAST
¿Qué vuelos salieron con más retraso? Hay 16.348 vuelos que aún no han despegado, así que su actual_departure, y por tanto el retraso calculado, es NULL:
SELECT flight_no, departure_airport, scheduled_departure,
EXTRACT(EPOCH FROM actual_departure - scheduled_departure) / 60 AS delay_min
FROM flights
ORDER BY delay_min DESC NULLS LAST
LIMIT 5;
| flight_no | departure_airport | scheduled_departure | delay_min |
|---|---|---|---|
| PG0589 | PEE | 2026-07-25 15:30:00 | 277 |
| PG0164 | DME | 2026-07-25 15:25:00 | 268 |
| PG0364 | KRR | 2026-07-15 11:45:00 | 267 |
| PG0568 | OVS | 2026-08-09 16:15:00 | 260 |
| PG0096 | URJ | 2026-07-30 16:35:00 | 258 |
Cámbialo a NULLS FIRST y lo primero de la lista son vuelos sin ningún dato de retraso:
| flight_no | departure_airport | scheduled_departure | delay_min |
|---|---|---|---|
| PG0402 | DME | 2026-09-05 12:25:00 | NULL |
| PG0405 | DME | 2026-08-12 09:35:00 | NULL |
| PG0404 | DME | 2026-08-12 19:05:00 | NULL |
Qué pasa si no escribes ninguna de las dos depende de la base de datos:
| base de datos | NULL con ASC | NULL con DESC |
|---|---|---|
| PostgreSQL, Oracle | al final | al principio |
| DuckDB | al final | al final |
| MySQL, SQL Server, SQLite | al principio | al final |
Así que el mismo ORDER BY delay_min DESC LIMIT 5 devuelve el top 5 real en DuckDB, pero cinco NULL en PostgreSQL. PostgreSQL, Oracle, DuckDB y SQLite (desde la 3.30) aceptan NULLS FIRST/NULLS LAST. MySQL y SQL Server no, y son justo dos de los motores más usados en empresas de España y Latinoamérica; ahí ordena primero por un indicador: ORDER BY delay_min IS NULL, delay_min DESC en MySQL, u ORDER BY CASE WHEN delay_min IS NULL THEN 1 ELSE 0 END, delay_min DESC en SQL Server. La guía de NULL en SQL cuenta más sobre cómo se comporta NULL.
ORDER BY con LIMIT: las N primeras filas
ORDER BY más LIMIT es la forma de sacar “los N primeros”. Los cuatro destinos más frecuentes desde Sheremétievo (SVO):
SELECT arrival_airport, COUNT(*) AS flights
FROM flights
WHERE departure_airport = 'SVO'
GROUP BY arrival_airport
ORDER BY flights DESC, arrival_airport
LIMIT 4;
| arrival_airport | flights |
|---|---|
| LED | 305 |
| BZK | 183 |
| GOJ | 122 |
| PES | 122 |
Empates en el corte
Tres aeropuertos, GOJ, PES y ULV, tienen 122 vuelos cada uno, y en el top 4 solo caben dos. Sin la segunda columna de orden, arrival_airport, la base de datos puede elegir dos cualesquiera, y lo hace: cinco ejecuciones de la consulta solo con ORDER BY flights DESC dieron cinco parejas distintas en 3.º y 4.º puesto (PES y ULV, GOJ y PES, ULV y PES, GOJ y ULV, PES y GOJ). Un informe que cambia en cada actualización es un error. Dos soluciones:
- Añadir un desempate que haga el orden único, como arriba. El resultado es estable, pero el corte sigue siendo arbitrario: ULV se queda fuera solo por su nombre.
- Quedarte con todas las filas empatadas. PostgreSQL 13+ y Oracle admiten
FETCH FIRST 4 ROWS WITH TIES, y SQL Server tieneTOP 4 WITH TIES. DuckDB y MySQL no tienen ninguna de las dos; usaRANK():
SELECT arrival_airport, flights
FROM (
SELECT arrival_airport, COUNT(*) AS flights,
RANK() OVER (ORDER BY COUNT(*) DESC) AS rnk
FROM flights
WHERE departure_airport = 'SVO'
GROUP BY arrival_airport
) t
WHERE rnk <= 4
ORDER BY flights DESC, arrival_airport;
| arrival_airport | flights |
|---|---|
| LED | 305 |
| BZK | 183 |
| GOJ | 122 |
| PES | 122 |
| ULV | 122 |
Cinco filas, porque tres aeropuertos comparten el 3.º puesto. La diferencia entre RANK(), DENSE_RANK() y ROW_NUMBER() en estos casos la explica la guía de ROW_NUMBER, RANK y DENSE_RANK; para el top N dentro de cada grupo (las 3 rutas principales de cada aeropuerto, por ejemplo), mira las funciones de ventana.
LIMIT en otras bases de datos
| base de datos | los 3 primeros |
|---|---|
| PostgreSQL, DuckDB, MySQL, SQLite | ORDER BY range DESC LIMIT 3 |
| SQL Server | SELECT TOP 3 ... ORDER BY range DESC |
| SQL estándar (PostgreSQL, Oracle 12c+, SQL Server, DuckDB) | ORDER BY range DESC FETCH FIRST 3 ROWS ONLY |
Paginación con OFFSET
OFFSET se salta filas antes de que LIMIT empiece a contar. La página 2 de la lista de aviones, a tres por página:
SELECT model, range
FROM aircrafts
ORDER BY range DESC
LIMIT 3 OFFSET 3;
| model | range |
|---|---|
| Airbus A320-200 | 5700 |
| Airbus A321-200 | 5600 |
| Boeing 737-300 | 4200 |
La forma estándar OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY devuelve las mismas filas y funciona también en SQL Server, donde OFFSET exige un ORDER BY. Paginar solo tiene sentido con un orden único: con empates, una fila puede salir en dos páginas o en ninguna. En tablas grandes, los desplazamientos profundos son lentos porque la base de datos tiene que generar y saltarse todas las filas anteriores; las aplicaciones suelen pasar a la paginación por clave, keyset (WHERE range < last_seen_value ORDER BY range DESC LIMIT 3).
Ordenar texto: números guardados como texto, mayúsculas, tildes y ñ
El texto se compara carácter a carácter, así que los números guardados como texto se ordenan “mal”. Los números de asiento del Airbus A319 son texto, como 1A o 10C:
SELECT seat_no FROM seats WHERE aircraft_code = '319' ORDER BY seat_no LIMIT 8;
| seat_no |
|---|
| 10A |
| 10B |
| 10C |
| 10D |
| 10E |
| 10F |
| 11A |
| 11B |
La fila 10 va antes que la 1 porque “1” seguido de “0” va antes que “1” seguido de “A”. Ordena por la parte numérica y después por la letra:
SELECT seat_no, fare_conditions
FROM seats
WHERE aircraft_code = '319'
ORDER BY CAST(regexp_extract(seat_no, '^[0-9]+') AS INTEGER), right(seat_no, 1)
LIMIT 8;
| seat_no | fare_conditions |
|---|---|
| 1A | Business |
| 1C | Business |
| 1D | Business |
| 1F | Business |
| 2A | Business |
| 2C | Business |
| 2D | Business |
| 2F | Business |
En PostgreSQL, cambia regexp_extract(seat_no, '^[0-9]+') por substring(seat_no FROM '^[0-9]+'). El mismo problema aparece con identificadores e importes cargados como texto: '10' < '9'. La solución de verdad es guardarlos como números.
Las mayúsculas son la otra sorpresa. DuckDB compara el texto por códigos de carácter, así que todas las mayúsculas van antes que todas las minúsculas:
SELECT x FROM (VALUES ('banana'), ('Cherry'), ('apple'), ('Apple')) t(x)
ORDER BY x;
| x |
|---|
| Apple |
| Cherry |
| apple |
| banana |
PostgreSQL con una intercalación habitual como es_ES.UTF-8 o en_US.UTF-8 devuelve apple, Apple, banana, Cherry; MySQL y SQL Server, con sus intercalaciones por defecto que no distinguen mayúsculas, tratan apple y Apple como iguales. Para tener el mismo orden en todas partes, ordena por lower(x) y usa x como desempate:
| x |
|---|
| Apple |
| apple |
| banana |
| Cherry |
Con texto en español el orden por códigos falla todavía más: las letras con tilde y la ñ van detrás de la z.
SELECT x FROM (VALUES ('Ñandú'), ('Ávila'), ('zorro'), ('Oviedo'), ('Nápoles'), ('Murcia')) t(x)
ORDER BY x;
| x |
|---|
| Murcia |
| Nápoles |
| Oviedo |
| zorro |
| Ávila |
| Ñandú |
La solución es pedir una intercalación española. En DuckDB, con la extensión ICU cargada, basta con ORDER BY x COLLATE es:
| x |
|---|
| Ávila |
| Murcia |
| Nápoles |
| Ñandú |
| Oviedo |
| zorro |
Ávila vuelve a la A y la ñ va entre la n y la o, como en el diccionario. En PostgreSQL el equivalente es ORDER BY x COLLATE "es-x-icu" (o una base creada con es_ES.UTF-8), en SQL Server COLLATE Modern_Spanish_CI_AS y en MySQL 8 COLLATE utf8mb4_es_0900_ai_ci. Ojo con quitar tildes sin más: COLLATE noaccent.nocase en DuckDB trata la ñ como una n y pone “Ñandú” antes que “Nápoles”.
Orden aleatorio
Para sacar filas al azar, ordena por un número aleatorio:
SELECT member_id, signup_channel
FROM loyalty_members
ORDER BY random()
LIMIT 3;
| member_id | signup_channel |
|---|---|
| 43351 | app |
| 34362 | web |
| 42005 | app |
Tus tres filas serán otras; de eso se trata. La función es random() en PostgreSQL, DuckDB y SQLite, RAND() en MySQL, y SQL Server usa ORDER BY NEWID(). Ordenar una tabla grande entera para quedarte con tres filas sale caro; para muestrear una tabla grande, PostgreSQL y DuckDB ofrecen TABLESAMPLE (y DuckDB también USING SAMPLE).
Errores frecuentes
- Fiarse del orden “natural”. Sin
ORDER BY, el orden es el que le venga bien al motor, y puede cambiar entre ejecuciones o tras una actualización. ORDER BYdentro de una subconsulta o vista. La mayoría de bases de datos no prometen conservarlo en la consulta exterior (SQL Server lo rechaza sinTOP). Ordena en la consulta más externa.DESCen una columna esperando que se aplique a todas. Cada columna necesita su propia dirección.- No poner desempate antes de
LIMIT. Los empates hacen inestables el top N y la paginación. - Olvidar dónde van los
NULL. EscribeNULLS LASToNULLS FIRSTsiempre que la columna pueda estar vacía. - Ordenar números guardados como texto. Conviértelos, o corrige el tipo de la columna.
- Ordenar nombres en español sin intercalación. Las tildes y la ñ acaban al final de la lista.
Estas reglas vuelven a salir en las consultas SQL básicas y en la chuleta de SQL.
Preguntas frecuentes
¿Cuál es el orden por defecto de ORDER BY?
Ascendente (ASC): primero los números más pequeños, las fechas más antiguas y el principio del alfabeto. DESC lo invierte. Dónde van los NULL por defecto depende de la base de datos, así que indícalo con NULLS FIRST o NULLS LAST.
¿Cómo ordeno por varias columnas?
Ponlas separadas por comas, cada una con su dirección: ORDER BY maker ASC, range DESC. La segunda columna solo decide el orden entre filas que son iguales en la primera.
¿Puedo usar un alias en ORDER BY?
Sí. ORDER BY se ejecuta después de SELECT, así que SELECT COUNT(*) AS flights ... ORDER BY flights DESC funciona en todas las bases de datos principales. Lo que no puedes es usar el alias en WHERE, que se ejecuta antes que SELECT.
¿Cómo pongo los NULL al final?
Añade NULLS LAST detrás de la columna: ORDER BY delay_min DESC NULLS LAST. En MySQL y SQL Server, que no tienen esta sintaxis, ordena primero por un indicador de “es nulo”, por ejemplo ORDER BY delay_min IS NULL, delay_min DESC en MySQL.
¿Cómo ordeno correctamente palabras con tildes y ñ?
Usa una intercalación española: COLLATE es en DuckDB con ICU, COLLATE "es-x-icu" en PostgreSQL, Modern_Spanish_CI_AS en SQL Server o utf8mb4_es_0900_ai_ci en MySQL 8. Sin ella, “Ávila” y “Ñandú” quedan detrás de la z.
¿Puedo ordenar por una columna que no está en el SELECT?
Sí, salvo con SELECT DISTINCT (en PostgreSQL y SQL Server) o cuando la columna ya no está disponible después de GROUP BY. En una consulta agrupada puedes ordenar por una columna agrupada o por un agregado como COUNT(*).
¿ORDER BY hace más lenta la consulta?
Ordenar cuesta tiempo con resultados grandes, sobre todo sin LIMIT. Con ORDER BY ... LIMIT n las bases de datos usan una ordenación top N que solo guarda n filas en memoria, y un índice sobre la columna de orden puede evitar la ordenación por completo en PostgreSQL, MySQL o SQL Server.
También en English, Português (Brasil), Tiếng Việt, 简体中文