SQL · Capítulo 1 · Lección de conceptos · 6 min de lectura

Qué hace de verdad un SELECT

Una consulta no se lee de arriba abajo. La base de datos ejecuta sus cláusulas en un orden fijo y construye cada columna fila por fila, y ese orden explica casi todas las sorpresas del principio.

La idea

Una consulta se lee como una frase, pero la base de datos no la ejecuta en ese orden. Corre las cláusulas en una secuencia fija: primero decide qué tabla mirar (FROM), luego qué filas conservar (WHERE), y solo después construye las columnas que pediste (SELECT). Ordenar y recortar la lista vienen al final.

SELECT en sí es simple. Por cada fila que sobrevivió produce una fila de salida, cuyos valores son columnas que copias, expresiones que calculas, o ambas. Nunca agrega, quita ni reordena filas. De eso se encargan las cláusulas que lo rodean.

Figura 1

El orden en que corre una consulta, no el orden en que la escribes

  1. FROM products elige la tabla
  2. WHERE category = 'Furniture' conserva las filas que cumplen
  3. GROUP BY / HAVING solo si los escribiste
  4. SELECT unit_price - unit_cost AS margin construye las columnas
  5. ORDER BY margin DESC ordena lo que queda
  6. LIMIT 3 corta la lista

Paso 1 de 5 Escribes SELECT primero, pero casi es lo último en ejecutarse. La base de datos empieza por FROM: ¿con qué tabla trabajamos?

  1. Escribes SELECT primero, pero casi es lo último en ejecutarse. La base de datos empieza por FROM: ¿con qué tabla trabajamos?
  2. WHERE recorre las filas y conserva las que cumplen. En este punto las columnas de salida todavía no existen, solo las de la propia tabla.
  3. Si la consulta tiene GROUP BY y HAVING, se ejecutan aquí, entre el filtro y la salida. De ellos trata el capítulo 4.
  4. Solo ahora SELECT construye la salida, un valor por fila. El alias margin nace en este paso, por eso WHERE no puede verlo.
  5. ORDER BY corre después de SELECT, así que puede ordenar por el alias margin. Al final llega LIMIT, que se queda con las primeras filas de la lista ordenada.

Por qué existe

Imagina a una persona haciendo esto a mano con una lista de productos impresa. No escribiría primero la columna de la respuesta. Buscaría la hoja, tacharía las filas que no importan y solo entonces calcularía el margen de las que quedan. SQL es declarativo: describes el resultado y la base de datos decide cómo obtenerlo. El orden fijo es lo que hace que tu descripción no sea ambigua. Sea cual sea el motor, WHERE category = 'Furniture' significa "decide esto antes de calcular nada".

El orden también mantiene las consultas rápidas. Filtrar primero significa que la base de datos calcula el margen solo de las filas que conservas y no de cada línea de pedido de la tienda. Y como FROM va primero, todas las demás cláusulas pueden usar las columnas de la tabla sin decir de dónde vienen.

Visto así, una consulta es una pequeña línea de producción: cada cláusula toma lo que dejó la anterior y le pasa a la siguiente una tabla más pequeña o con otra forma. Nunca ves las tablas intermedias, pero siempre puedes reconstruirlas en tu cabeza, y así depuran los analistas.

Cómo funciona por dentro

Sigue esta consulta por la máquina:

SELECT name, unit_price - unit_cost AS margin
FROM products
WHERE category = 'Furniture'
ORDER BY margin DESC
LIMIT 3;
  1. FROM abre products: todas las filas, todas las columnas.
  2. WHERE prueba category = 'Furniture' en cada fila y conserva las que pasan.
  3. SELECT construye la salida. Por cada fila conservada copia name, calcula la resta y llama margin al resultado.
  4. ORDER BY ordena las filas de salida. Corre después de SELECT, así que el alias margin ya existe.
  5. LIMIT se queda con las tres primeras filas de la lista ordenada.

Las expresiones trabajan fila por fila

unit_price - unit_cost nunca mira la fila de arriba ni la de abajo. La resta se hace una vez por fila, con los dos valores de esa fila. Por eso un SELECT con una expresión devuelve tantas filas como tiene la tabla después de filtrar. No resume nada. Los resúmenes son el capítulo 4.

Figura 2

SELECT calcula un valor nuevo por fila, y el NULL se contagia

products
product_idnameunit_priceunit_cost
101Vale Velvet Loveseat1659.00NULL
102Alder Oak Coffee Table919.00503.50
103Orchard Rattan Dining Chair789.00429.87
Resultado
namemargin
Vale Velvet LoveseatNULL
Alder Oak Coffee Table415.50
Orchard Rattan Dining Chair359.13

Paso 1 de 4 Tres filas de products. El loveseat tiene precio pero no costo registrado: su unit_cost es NULL, es decir, desconocido.

  1. Tres filas de products. El loveseat tiene precio pero no costo registrado: su unit_cost es NULL, es decir, desconocido.
  2. SELECT name, unit_price - unit_cost AS margin toma una fila a la vez. Para la mesa de centro: 919.00 - 503.50 = 415.50.
  3. Lo mismo con la silla de comedor: 789.00 - 429.87 = 359.13. La tabla conserva sus tres filas, solo cambia la forma.
  4. Para el loveseat la cuenta es 1659.00 menos un valor desconocido. La respuesta también es desconocida, así que margin es NULL. La fila no se descarta y tampoco vale 1659.00.

NULL es un valor con el que no se puede calcular

Un NULL no es cero ni una cadena vacía. Marca "desconocido", y cualquier cuenta con un valor desconocido da un valor desconocido. En Harbor & Pine algunos productos no tienen costo registrado, así que su margen es NULL. Lo mismo pasa al pegar texto: 'Price: ' || NULL es NULL. COALESCE(unit_cost, 0) cambia un NULL por un valor que tú eliges, y eso es una decisión de negocio. Tómala a propósito.

Lo que puedes mostrar

Como SELECT construye la salida, puedes poner en ella cualquier cosa calculada a partir de la fila: una columna, un cálculo, un valor redondeado, un texto armado a pedazos. Cada una se convierte en una columna del resultado con el nombre que le des. Sin alias, la base de datos inventa un nombre como unit_price - unit_cost, incómodo en una hoja de cálculo. Ponle nombre a cada columna calculada.

Leer una consulta en el orden en que corre

Cuando un resultado se ve mal, no leas desde arriba. Recorre la consulta como lo hace la máquina. Ejecuta solo SELECT * FROM products WHERE category = 'Furniture' y mira las filas. ¿Coinciden con lo que esperabas? Luego agrega la expresión y revisa un producto a mano: 919.00 - 503.50 debe dar 415.50. Luego agrega el orden, luego el límite. Cada paso cambia una sola cosa, así que un número equivocado siempre apunta a la cláusula que agregaste al final. Este hábito vale más que cualquier función que aprendas en este capítulo.

Alias y DISTINCT

AS margin renombra una columna de salida. Hace legible el resultado y da a las cláusulas posteriores un nombre corto, pero solo las cláusulas que corren después de SELECT pueden usarlo. ORDER BY puede. En Postgres, MySQL y SQL Server WHERE no puede, y la solución es repetir la expresión. Algunos motores, DuckDB incluido, lo toleran, así que no cuentes con ello. De igual modo, SELECT DISTINCT category se ejecuta sobre las filas de salida después de SELECT: quita duplicados entre los valores que elegiste, no entre las filas de la tabla.

Errores comunes

"SELECT corre primero, porque lo escribo primero"

Se escribe primero porque a las personas nos gusta empezar por la respuesta. La base de datos empieza por FROM. Por eso no puedes usar un alias de columna en WHERE, y por eso SELECT no puede hacer desaparecer filas. Cuando una consulta se comporta raro, léela en el orden en que corre, no en el que se escribe.

"SELECT * es lo mismo que listar las columnas"

Hoy devuelve las mismas filas, pero cambia sin avisar el día que alguien agrega o reordena una columna, y mueve datos que nunca usas. Nombrar las columnas además documenta para qué sirve la consulta. Usa * para explorar y nombra las columnas en todo lo que guardes.

"Un costo faltante cuenta como cero"

La base de datos nunca lo decidirá por ti. NULL atraviesa la aritmética y sale como NULL. Si lo tratas en silencio como cero, el loveseat parece un producto con un margen de $1,659 completos. Pregúntate si desconocido de verdad significa cero y, si es así, dilo con COALESCE.

Ponte a prueba

Una pregunta. Al responderla, la lección queda marcada en el mapa; nunca bloquea un nivel.

El producto 101 tiene unit_price 1659.00 y un unit_cost en NULL. ¿Qué muestra SELECT unit_price - unit_cost AS margin FROM products para él?

Elige qué puede ejecutarse en este sitio. Puedes cambiarlo cuando quieras desde "Configurar cookies", al pie de cada página.