SELECT, filtros y orden
La consulta más simple ya tiene todo lo que después complica: un orden de evaluación que no es el orden en que se escribe, nulos que no se comparan y un ORDER BY que no es gratis.
Para este tema conviene tener claro:Tablas, claves e integridad referencial
SELECT columnas FROM tabla WHERE condición es la primera línea de SQL que uno escribe y la que
más veces va a escribir. Vale la pena entender qué hace el motor con ella, porque los tres errores
más comunes de SQL ya están todos acá adentro.
Se escribe en un orden y se evalúa en otro
SQL se escribe en un orden y se evalúa en otro. Primero FROM, después WHERE, después
GROUP BY y HAVING, recién ahí SELECT, y por último ORDER BY y LIMIT.
Eso explica dos cosas que desconciertan al principio. Un alias definido en el SELECT no se puede
usar en el WHERE, porque cuando el filtro corre el alias todavía no existe. Y sí se puede usar en
el ORDER BY, que corre después. No es un capricho del dialecto: es el orden lógico de evaluación.
Así se escribe la consulta: SELECT primero, FROM después, y el resto abajo.
Antes de seguir, predecí
SELECT * y por qué no va en código
SELECT * es cómodo para explorar y mala idea en código. Trae columnas que no se usan —más red,
más memoria—, se rompe silenciosamente cuando alguien agrega o reordena columnas, e impide que el
motor resuelva la consulta leyendo sólo un índice.
Esa última es la más concreta: si un índice contiene todas las columnas pedidas, el motor responde
sin tocar la tabla. Con *, nunca puede.
El nulo, fuente número uno de errores silenciosos
El nulo es la fuente de errores silenciosos número uno. No es un valor, es la ausencia de valor, y cualquier comparación con él da desconocido, que no es verdadero.
Por eso WHERE estado = NULL no devuelve nada nunca, y hay que escribir IS NULL. Y por eso
WHERE estado <> 'activo' excluye las filas donde el estado es nulo, que casi nunca es lo que
se quiso decir. Si esas filas tienen que entrar, hay que pedirlo explícitamente con un
OR estado IS NULL.
Una función sobre la columna anula el índice
Los filtros tienen una regla práctica que vale más que cualquier truco: si la columna está envuelta
en una función, el índice no se usa. WHERE YEAR(fecha) = 2026 recorre toda la tabla;
WHERE fecha >= '2026-01-01' AND fecha < '2027-01-01' usa el índice y da lo mismo.
Lo mismo con LIKE '%texto%': el comodín al principio impide aprovechar el orden del índice, que
está armado por prefijo. LIKE 'texto%' sí lo aprovecha. Para búsquedas dentro del texto hacen
falta índices de texto completo, no LIKE.
ORDER BY no es gratis
ORDER BY no es gratis: si no hay un índice que ya entregue las filas en ese orden, el motor tiene
que ordenar el resultado, y si no entra en memoria lo hace en disco.
Hay una trampa específica con LIMIT. Paginar con LIMIT 20 OFFSET 100000 obliga a producir y
descartar cien mil filas: la página cien es mucho más cara que la primera. La alternativa es paginar
por clave —“traeme los veinte siguientes a este valor”—, que usa el índice y cuesta lo mismo en
cualquier página.
Sin ORDER BY no hay orden
Un detalle que muerde en producción: sin ORDER BY, el orden de las filas no está definido. Puede
parecer estable durante meses y cambiar cuando el motor elige otro plan o los datos se reorganizan.
Y si el ORDER BY no desempata —ordenar por fecha cuando hay muchas filas con la misma fecha—, la
paginación puede repetir o saltear filas entre páginas. La regla es incluir siempre una columna
única al final del orden.
La misma consulta, bien y mal
-- Mal: trae columnas que no se usan y aplica una función sobre la columna filtrada
SELECT *
FROM invoices
WHERE YEAR(issued_at) = 2026
ORDER BY issued_at DESC;
-- Bien: pide lo que necesita y deja la columna sin tocar, para que el índice sirva
SELECT id, customer_id, total, issued_at
FROM invoices
WHERE issued_at >= '2026-01-01'
AND issued_at < '2027-01-01'
ORDER BY issued_at DESC
LIMIT 50;Los dos filtros piden lo mismo y sólo el segundo puede usar un índice sobre issued_at. Aplicar
YEAR() sobre la columna produce un valor que no está indexado, así que el motor tiene que
calcularlo para cada fila de la tabla. Convertir un filtro sobre una función en un filtro por
rango es la reescritura que más veces salva una consulta lenta.
El LIMIT sin ORDER BY merece una advertencia aparte: sin orden explícito, qué 50 filas
devuelve el motor no está definido, y puede cambiar entre ejecuciones o entre versiones. Un
LIMIT sin ORDER BY es una consulta no determinista aunque parezca estable.
Lo que decide si una consulta es rápida
| Escrito así | El motor puede usar el índice | Por qué |
|---|---|---|
| fecha >= 'x' AND fecha < 'y' | sí | el rango se resuelve bajando por el árbol |
| YEAR(fecha) = 2026 | no | el valor calculado no está indexado |
| nombre LIKE 'ana%' | sí | hay prefijo por donde bajar |
| nombre LIKE '%ana' | no | sin prefijo, hay que mirar todo |
| id IN (1, 2, 3) | sí | son tres búsquedas por índice |
| estado != 5 | casi nunca | la negación abarca casi toda la tabla |
Cierre
El orden de evaluación explica dónde vale cada alias; el nulo exige IS NULL y cuidado con las
desigualdades; envolver la columna en una función mata el índice; y sin ORDER BY con desempate no
hay orden garantizado ni paginación confiable.
Autoevaluación
¿Lo entendiste?
Práctica