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

Aviones alineados en una pista del más pequeño al más grande, con marcas de posición numeradas

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:

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:

  1. 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.
  2. Quedarte con todas las filas empatadas. PostgreSQL 13+ y Oracle admiten FETCH FIRST 4 ROWS WITH TIES, y SQL Server tiene TOP 4 WITH TIES. DuckDB y MySQL no tienen ninguna de las dos; usa RANK():
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

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, 简体中文