ORDER BY ordena el resultado final de una consulta. Lo normal es que vaya al final de la sentencia (después de SELECT … FROM … WHERE … GROUP BY … HAVING …), y si lo usamos, controla el orden en que llegan las filas a la aplicación o al usuario.

Tres ideas que repetimos:

  • ORDER BY ordena la salida, no la tabla.

  • ASC es el valor por defecto: si no indicamos nada, se asume ascendente.

  • Podemos ordenar por varias columnas y mezclar direcciones (ASC/DESC) por columna.

Ejemplo mínimo:

SELECT id, nombre, creado_en
FROM pedidos
WHERE estado = 'enviado'
ORDER BY creado_en DESC; -- Más recientes primero

Cuándo lo usamos: listados, paneles con “los últimos X”, exportaciones ordenadas, informes con “top N”, y sobre todo cualquier endpoint paginado que deba ser estable en el tiempo.

ASC vs DESC: cuándo nos conviene cada uno

  • ASC (ascendente): de menor a mayor / de A a Z / de antiguo a reciente. Útil para series temporales cuando queremos cronología.

  • DESC (descendente): de mayor a menor / de Z a A / de reciente a antiguo. Ideal para dashboards, “últimos registros”, rankings.

Muddle dice : si omitimos la dirección en una columna intermedia, se aplica ASC solo a esa columna, no a toda la cláusula.

-- Mezcla de direcciones sin necesidad de repetir ASC
ORDER BY prioridad DESC, fecha, id DESC;

Ejemplo de cómo ordenar por varias columnas

Cuando hay empates en la primera columna, el orden relativo entre filas empatadas puede parecer azaroso. La solución profesional es añadir una o varias columnas de desempate para que la ordenación sea determinista entre ejecuciones.

Patrón que usamos muchísimo en listados:

-- Primero por oficio (A–Z), luego por salario (mayor a menor), y por último por id (A–Z)
SELECT apellido, oficio, salario, emp_no
FROM emp
ORDER BY oficio ASC, salario DESC, emp_no ASC;

Y para feeds o timelines, solemos desempatar por identificador monotónico:

-- “Más reciente primero” y, si dos filas comparten timestamp, gana el id mayor
ORDER BY created_at DESC, id DESC;

4) Paginación con ORDER BY: LIMIT/OFFSET, OFFSET/FETCH y ROWNUM

La paginación sin ORDER BY es un anti-patrón: cada página puede devolver filas distintas en cada llamada. Siempre paginamos con un ORDER BY estable.

PostgreSQL / MySQL / SQLite (paginación clásica):

SELECT ...
FROM ...
WHERE ...
ORDER BY created_at DESC, id DESC
LIMIT 20 OFFSET 40; -- Página 3 de 20 por página

SQL Server (sintaxis moderna):

SELECT ...
FROM ...
ORDER BY created_at DESC, id DESC
OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY;

Oracle (moderno):

SELECT ...
FROM ...
ORDER BY created_at DESC, id DESC
OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY;

Notas de oficio

  • Evitamos OFFSET grandes en tablas enormes (coste O(n)). Para alto volumen, preferimos keyset pagination: WHERE (created_at, id) < (:ts, :id) con el mismo ORDER BY.

  • Si la API exige offsets, añadimos índices que cubran las columnas del ORDER BY y de los filtros.

5) NULLS FIRST/LAST y diferencias por motor

Resumen rápido (ASC):

  • PostgreSQL/Oracle: NULL suele ir al final con ASC (y al principio con DESC). Podemos forzar con NULLS FIRST/LAST.

  • MySQL/SQLite/SQL Server: NULL con ASC suele ir al principio (y al final con DESC).

Forzar posición de NULL donde se admite:

-- PostgreSQL / Oracle
ORDER BY fecha ASC NULLS LAST;
ORDER BY fecha DESC NULLS FIRST;

Emular en motores sin NULLS FIRST/LAST:

-- Funciona en MySQL/SQLite/SQL Server
ORDER BY (fecha IS NULL) ASC, fecha ASC; -- NULL al final

PostgreSQL

Admite NULLS FIRST/LAST, índices en orden DESC nativo, y COLLATE por columna/expresión.

MySQL y MariaDB

No tienen NULLS FIRST/LAST estándar (se emula con booleanos). Ojo a la collation elegida (ver sección 6).

SQL Server (Microsoft)

OFFSET/FETCH para paginar. Collations muy configurables a nivel de base/columna/expresión.

SQLite

Sencillo y práctico; cuidado con tipos de datos dinámicos y cómo compara NULL.

Oracle

NULLS FIRST/LAST soportado y paginación moderna con OFFSET … FETCH.

6) Collations y orden alfabético en español (es_ES): tildes, ñ y mayúsculas/minúsculas

En listados con apellidos y nombres en español de España, buscamos que Á, É, Í, Ó, Ú y Ñ se ordenen como corresponde al idioma. El truco es forzar la collation en la expresión (o en la columna) en lugar de confiar en la collation por defecto.

PostgreSQL (ejemplo):

-- Según sistema/ICU: "es_ES", "es_ES.utf8", etc.
SELECT apellido
FROM personas
ORDER BY apellido COLLATE "es_ES" ASC, id ASC;

MySQL 8+ (ejemplos comunes):

-- Para sensibilidad a acentos y no a mayúsculas: _ci (case-insensitive), _ai (accent-insensitive)
ORDER BY apellido COLLATE utf8mb4_es_0900_ai_ci ASC;
-- En versiones previas: utf8mb4_spanish_ci o utf8mb4_spanish2_ci

SQL Server:

ORDER BY apellido COLLATE Modern_Spanish_CI_AI ASC;

Consejo: si el proyecto sirve a varios países, alineamos la collation con la regla de negocio del listado (p. ej., un CRM para España vs. otro para LATAM) y la documentamos en la capa de acceso a datos.

Rendimiento: cuándo ORDER BY es caro y cómo lo optimizamos

  • Evitar “sort” innecesario: si el plan puede usar un índice que ya “trae” el orden (INDEX(departamento ASC, salario DESC, id ASC)), ahorramos CPU y memoria.

  • Dirección del índice importa: en motores que la soportan, casamos DESC en el índice con DESC en la consulta.

  • Función en el ORDER BY = adiós índice (salvo índices funcionales). Mejor pre-calcular o indexar la expresión.

  • Paginación profunda: OFFSET grandes fuerzan a leer y descartar. Consideramos keyset pagination.

  • JOIN + ORDER BY: indexamos tanto las claves de unión como las columnas del ORDER BY. En multi-columna, el orden del índice debe seguir el patrón de consulta más frecuente.

Trucos que usamos a menudo: por posición, por CASE y “orden natural”

Orden por posición en el SELECT (útil en reportes rápidos):

SELECT categoria, total
FROM ventas
ORDER BY 2 DESC, 1 ASC; -- total, luego categoria

Orden personalizado con CASE (prioridades de negocio):

ORDER BY CASE prioridad
WHEN 'VIP' THEN 1
WHEN 'ALTA' THEN 2
WHEN 'MEDIA' THEN 3
ELSE 4
END, fecha DESC, id DESC;

“Orden natural” de números guardados como texto:

-- Evitar que '10' vaya antes que '2'
ORDER BY LENGTH(codigo), codigo; -- MySQL/SQLite
-- o convertir:
ORDER BY CAST(codigo AS INTEGER); -- Postgres/SQL Server/Oracle

Errores típicos y cómo los evitamos

  • No determinismo: ORDER BY solo por una columna con muchos empates. Solución: añadimos desempate (id/timestamp).

  • Paginación sin índice: tarda y “salta” filas con escrituras concurrentes. Solución: índice compuesto que cubra filtros + columnas del orden.

  • Confiar en el “orden natural”: ningún SGBD garantiza orden sin ORDER BY. Siempre lo especificamos.

  • Collation incorrecta: listados con tildes/ñ mal ordenados. Solución: COLLATE explícito y tests con apellidos típicos (García, Muñoz, Iñíguez, Núñez).

  • Ordenar por expresión no indexada: recalcula por fila. Solución: índice funcional o columna materializada.

Ejemplos completos listos para copiar

Listado por oficio/salario/id (desempate estable):

SELECT apellido, oficio, salario, emp_no
FROM emp
ORDER BY oficio ASC, salario DESC, emp_no ASC;

Feed cronológico (keyset pagination):

-- Página siguiente de un feed estable
SELECT id, created_at, titulo
FROM posts
WHERE (created_at, id) < (:ultimo_ts, :ultimo_id)
ORDER BY created_at DESC, id DESC
LIMIT 20;

Orden alfabético correcto en español (España):

-- Postgres (variable según ICU/SO)
SELECT apellido
FROM personas
ORDER BY apellido COLLATE "es_ES" ASC, id ASC;
— SQL Server
SELECT apellido
FROM personas
ORDER BY apellido COLLATE Modern_Spanish_CI_AI, id ASC;

Paginación por motor (resumen práctico):

-- MySQL/Postgres/SQLite
... ORDER BY created_at DESC, id DESC LIMIT 50 OFFSET 0;
— SQL Server
ORDER BY created_at DESC, id DESC OFFSET 0 ROWS FETCH NEXT 50 ROWS ONLY;— Oracle
ORDER BY created_at DESC, id DESC OFFSET 0 ROWS FETCH NEXT 50 ROWS ONLY;

Preguntas

¿Puedo mezclar ASC y DESC?
Sí, en cada columna: ORDER BY prioridad DESC, fecha ASC.

¿Cómo fuerzo NULL al final si mi motor no tiene NULLS LAST?
ORDER BY (col IS NULL), col (primero FALSE=0, luego TRUE=1).

¿ORDER BY funciona dentro de una subconsulta?
Solo tiene efecto en el nivel donde aparece. Para asegurar orden en el resultado final, el ORDER BY va en la consulta externa.

¿Por qué mi paginación es lenta con OFFSET altos?
Porque el motor lee y descarta filas hasta llegar al offset. Considera keyset pagination o cursores.

Conclusión

Como práctica estándar, tratamos ORDER BY como un contrato con el frontend y con el usuario: siempre estable, alineado con negocio y eficiente gracias a índices que lo soporten. Cuando enseñamos a un perfil junior, repetimos tres mantras: va al final, ASC es el default y si hay empates, diseña un desempate. Con esos pilares, los listados dejan de “bailar” entre ejecuciones y las páginas cargan más rápido.

Privacy Preference Center