Cerraste el módulo 1 con la base de datos de TiendaVerde cargada y verificada, y con una promesa: la primera consulta real estaba a una lección de distancia. Ha llegado. En esta lección aprenderás la instrucción más importante de SQL, la que usarás el 90 % de las veces que te sientes delante de una base de datos: SELECT. Verás cómo pedir columnas concretas, por qué SELECT * es cómodo para explorar pero peligroso en producción, qué es realmente el conjunto de resultados que te devuelve el motor y —lo más importante a medio plazo— en qué orden lógico ejecuta PostgreSQL las cláusulas de una consulta, que no es el orden en el que las escribes. Ese último punto parece teórico hoy y será la clave para entender los módulos 4 y 7.

Contenido

  1. La consulta mínima: SELECT ... FROM ...
  2. Seleccionar columnas concretas
  3. SELECT *: cuándo sí y por qué en producción no
  4. El orden de las columnas lo decide el SELECT
  5. SELECT sin FROM: SQL como calculadora
  6. Qué es realmente el conjunto de resultados
  7. El orden lógico de ejecución de una consulta
  8. Leer resultados en psql y el modo expandido \x
  9. Errores Comunes y Consejos
  10. Ejercicios
  11. Conclusión

  1. La consulta mínima: SELECT ... FROM ...

Una consulta de lectura necesita, como mínimo, dos cláusulas:

SELECT nombre
FROM categorias;
nombre
Alimentación
Cosmética natural
Hogar sostenible
Bebidas
Higiene personal
Complementos

Léela en voz alta al revés y entenderás mejor lo que hace el motor: "de la tabla categorias, dame la columna nombre".

Cláusula Papel
SELECT <lista de columnas> Proyección: qué columnas quieres en el resultado
FROM <tabla> Origen: de dónde salen las filas

En la jerga del modelo relacional que viste en 01-05, SELECT hace una proyección (elegir columnas) y, cuando añadamos WHERE en la lección 02-03, haremos también una selección (elegir filas). Que la palabra clave se llame SELECT y haga la proyección es una de las pequeñas incoherencias históricas de SQL; conviene saberlo para no confundirse al leer literatura académica.

Fíjate en que no le has dicho a PostgreSQL cómo recorrer la tabla, ni en qué fichero está, ni si conviene usar un índice. Solo has descrito el qué. Eso es el carácter declarativo del lenguaje que vimos en 01-01.

  1. Seleccionar columnas concretas

Para pedir varias columnas se separan por comas:

SELECT id,
       nombre,
       precio
FROM productos;
id nombre precio
1 Aceite de oliva virgen extra 500 ml 12.50
2 Arroz integral ecológico 1 kg 3.90
3 Miel de azahar cruda 500 g 9.75
4 Pasta de espelta 500 g 2.80
5 Tomate triturado ecológico 400 g 1.95
6 Crema facial de aloe vera 50 ml 18.90
7 Champú sólido de romero 80 g 8.40
8 Aceite corporal de almendras 200 ml 14.25
9 Bálsamo labial de caléndula 15 ml 4.60
10 Detergente ecológico concentrado 1 L 11.20
11 Estropajo vegetal de luffa (pack 3) 5.50
12 Bolsas reutilizables de algodón (pack 5) 9.90
13 Velas de cera de soja (pack 2) 13.75
14 Infusión de manzanilla ecológica 20 uds 3.25
15 Té verde matcha ceremonial 30 g 22.00
16 Kombucha de jengibre 750 ml 4.95
17 Zumo de naranja prensado en frío 1 L 5.40
18 Cepillo de dientes de bambú 3.50
19 Desodorante natural en barra 50 g 7.80
20 Cápsulas de espirulina 120 uds 16.40

20 filas. Exactamente las 20 que cargaste en 01-06: SELECT sin filtro devuelve todas las filas de la tabla, y proyecta solo las columnas que has nombrado. Ni coste, ni stock, ni activo, ni fecha_alta aparecen: no los has pedido.

Detalles de escritura que ya conoces de 01-03 y que aquí se aplican:

  • La coma va entre columnas, nunca detrás de la última. SELECT id, nombre, FROM productos da syntax error at or near "FROM".
  • Poner una columna por línea no es capricho: cuando la lista crezca a quince columnas, agradecerás poder añadir o quitar una sin reescribir la línea entera.
  • Los nombres deben existir. Si escribes SELECT precio_venta FROM productos, PostgreSQL responde column "precio_venta" does not exist. Un \d productos te saca de dudas en un segundo.

  1. SELECT *: cuándo sí y por qué en producción no

El asterisco significa "todas las columnas de la tabla, en el orden en que se definieron":

SELECT * FROM categorias;
id nombre descripcion
1 Alimentación Productos ecológicos de alimentación seca y conservas
2 Cosmética natural Cosmética con ingredientes naturales y sin parabenos
3 Hogar sostenible Limpieza y menaje con materiales reutilizables
4 Bebidas Infusiones, zumos y fermentados ecológicos
5 Higiene personal Higiene diaria con envases reducidos o compostables
6 Complementos Suplementos alimenticios de origen vegetal

Es cómodo, y para explorar una tabla que no conoces es lo primero que se escribe. El problema aparece cuando ese * acaba dentro de una aplicación, un informe o una vista.

Riesgo de SELECT * en producción Qué ocurre en la práctica
Contrato frágil Si mañana alguien añade una columna a productos, tu código recibe una columna extra que no espera; si alguien la reordena, un acceso por posición te devuelve el dato equivocado
Tráfico y memoria innecesarios Traes descripcion (TEXT) aunque solo quieras nombre. Multiplicado por millones de filas, es dinero
Rompe optimizaciones Un índice que contiene todas las columnas que pides permite un index-only scan (módulo 8). Con * casi nunca es posible
Ilegible en revisión Quien lea la consulta no sabe qué datos necesita realmente
Ambigüedad con JOIN Con dos tablas unidas, * devuelve dos columnas llamadas nombre y ninguna forma limpia de distinguirlas (módulo 3)

Regla del curso: SELECT * para explorar en psql; lista explícita de columnas en cualquier consulta que vayas a guardar, versionar o ejecutar más de una vez.

Hay una variante intermedia que sí es habitual y correcta: SELECT tabla.* en consultas con varias tablas, para decir "todas las columnas de esta tabla". La verás en el módulo 3.

  1. El orden de las columnas lo decide el SELECT

El resultado no tiene por qué respetar el orden físico de la tabla. Manda tu lista:

SELECT descripcion,
       nombre,
       id
FROM categorias;
descripcion nombre id
Productos ecológicos de alimentación seca y conservas Alimentación 1
Cosmética con ingredientes naturales y sin parabenos Cosmética natural 2
Limpieza y menaje con materiales reutilizables Hogar sostenible 3
Infusiones, zumos y fermentados ecológicos Bebidas 4
Higiene diaria con envases reducidos o compostables Higiene personal 5
Suplementos alimenticios de origen vegetal Complementos 6

Y nada te impide repetir una columna, aunque rara vez sea útil:

SELECT id, id, nombre FROM categorias;

Devuelve tres columnas, dos de ellas llamadas id. PostgreSQL lo permite; los clientes y los lenguajes de programación que acceden por nombre, no siempre. Es un aviso de que en cuanto haya nombres repetidos harán falta alias, que es justo el tema de la lección siguiente.

  1. SELECT sin FROM: SQL como calculadora

En PostgreSQL la cláusula FROM es opcional. Sin ella, SELECT evalúa expresiones y devuelve una única fila:

SELECT 2 + 2;
?column?
4

Esa cabecera ?column? significa "no sé cómo llamar a esto": la expresión no tiene nombre. Se arregla con un alias (lección 02-02).

Ejemplos que usarás a diario para probar cosas antes de meterlas en una consulta grande:

SELECT current_date;                     -- fecha del servidor
SELECT 12.50 * 1.21;                     -- precio con IVA
SELECT 3 * 12.50 * (1 - 0.05);           -- importe de una línea con 5 % de descuento
SELECT 'Lucía' || ' ' || 'Martínez';     -- concatenación de texto
current_date
2026-08-02
?column?
15.1250
?column?
35.6250
?column?
Lucía Martínez

Tres cosas que ya se ven aquí y que reaparecerán:

  1. current_date devuelve la fecha del día en que ejecutes la consulta; el valor de arriba es solo un ejemplo.
  2. 12.50 * 1.21 da 15.1250, con cuatro decimales. PostgreSQL suma las escalas de los operandos al multiplicar NUMERIC. Para presentar dinero habrá que redondear (lección 02-02).
  3. 3 * 12.50 * (1 - 0.05) es el cálculo del importe de la línea 18 de lineas_pedido, con los números escritos a mano. En la lección siguiente lo escribirás con columnas en lugar de literales.

Nota de dialecto: Oracle exige un FROM, y por eso allí se escribe SELECT 2+2 FROM dual;. MySQL, SQL Server, SQLite y PostgreSQL admiten SELECT sin FROM.

  1. Qué es realmente el conjunto de resultados

Lo que devuelve una consulta se llama conjunto de resultados (result set) y, conceptualmente, es una relación: una tabla temporal, sin nombre, que existe solo mientras dura la consulta. De ahí se derivan tres propiedades que conviene interiorizar hoy:

  1. Es una tabla como cualquier otra. Tiene columnas con nombre y tipo, y filas. Por eso podrá usarse como origen de otra consulta (subconsultas en FROM, módulo 7) o combinarse con otra relación (UNION, módulo 3).
  2. No modifica nada. SELECT es una operación de solo lectura: por muchas veces que la ejecutes, la tabla original no cambia. Modificar datos es el módulo 5.
  3. No tiene orden garantizado. Esta es la que más sorprende.

Sobre el tercer punto: en el resultado de la sección 2 los productos salieron ordenados por id, del 1 al 20. Parece que la tabla "está ordenada". No lo está. Ese orden es un efecto colateral de que las filas se insertaron seguidas y PostgreSQL las leyó en orden físico. En cuanto la tabla crezca, se actualicen filas, entre en juego un índice o el motor use varios procesos en paralelo, el orden puede cambiar sin aviso y sin que nada falle.

La regla es tajante y no admite excepciones:

Si el orden importa, escribe ORDER BY. Si no lo escribes, no tienes derecho a esperar ningún orden concreto.

La cláusula ORDER BY es la lección 02-05. Hasta entonces, cuando en esta lección o en la siguiente veas resultados "ordenados por id", entiende que es el orden que probablemente verás, no el que el motor te promete.

  1. El orden lógico de ejecución de una consulta

Escribes una consulta en un orden y el motor la resuelve en otro. Entender esa diferencia es lo que después hace evidentes cosas que de otro modo parecen arbitrarias (por qué un alias funciona en ORDER BY pero no en WHERE, por qué HAVING existe además de WHERE, por qué una subconsulta puede ver ciertas columnas y otras no).

Orden en que se escribe:

SELECT   columnas
FROM     tabla
WHERE    condición
ORDER BY columnas
LIMIT    n;

Orden en que se ejecuta lógicamente:

flowchart LR
    A["1 · FROM<br/>de dónde salen las filas"] --> B["2 · WHERE<br/>qué filas se quedan"]
    B --> C["3 · SELECT<br/>qué columnas se proyectan"]
    C --> D["4 · ORDER BY<br/>en qué orden se devuelven"]
    D --> E["5 · LIMIT<br/>cuántas se entregan"]
Paso Cláusula Qué hace Lección
1 FROM Determina el conjunto de filas de partida 02-01
2 WHERE Descarta filas que no cumplen la condición 02-03
3 SELECT Calcula y proyecta las columnas del resultado 02-01 / 02-02
4 ORDER BY Ordena el resultado ya proyectado 02-05
5 LIMIT Recorta cuántas filas se entregan 02-06

De momento solo usas los pasos 1 y 3, así que el diagrama parece exagerado. Guárdalo: iremos añadiendo cláusulas a este mismo esquema en cada lección del módulo, y en el módulo 4 le incorporaremos GROUP BY y HAVING.

Dos consecuencias que ya puedes anticipar:

  • Como WHERE se ejecuta antes que SELECT, cuando llegue el momento de filtrar no podrás usar en WHERE un nombre inventado en el SELECT: todavía no existe.
  • Como ORDER BY se ejecuta después de SELECT, allí sí podrás usarlo.

Un matiz importante: esto es el orden lógico, el que define el significado de la consulta. El plan real de ejecución que elige el optimizador puede ser muy distinto (leer un índice, filtrar mientras lee, parar antes de tiempo), siempre que el resultado sea el mismo que el del orden lógico. Los planes reales se estudian en el módulo 8 con EXPLAIN.

  1. Leer resultados en psql y el modo expandido \x

Cuando ejecutas una consulta en psql ves algo así:

tiendaverde=> SELECT id, nombre, precio FROM productos LIMIT 6;
 id |               nombre                | precio
----+-------------------------------------+--------
  1 | Aceite de oliva virgen extra 500 ml |  12.50
...
(6 filas)

Piezas de esa salida:

Elemento Significado
Primera línea Nombres de las columnas del resultado
Línea de guiones Separador
Alineación Los números se alinean a la derecha, el texto a la izquierda: es una pista visual del tipo de dato
(6 filas) Cuántas filas ha devuelto la consulta. Míralo siempre: es la primera comprobación de si tu consulta hace lo que crees
Celda vacía Un NULL se muestra como espacio en blanco (configurable con \pset null '(null)')

Los booleanos se imprimen como t y f, no como true/false. En este curso los escribiremos como true/false en las tablas de resultados por legibilidad.

Cuando una fila tiene muchas columnas o textos largos, la salida se desmonta y se vuelve ilegible. Para eso está el modo expandido:

tiendaverde=> \x
Expanded display is on.
tiendaverde=> SELECT * FROM clientes;
-[ RECORD 1 ]---+---------------------------
id              | 1
nombre          | Lucía
apellidos       | Martínez Soler
email           | [email protected]
ciudad          | Valencia
pais            | España
fecha_registro  | 2025-01-10
referido_por_id |
-[ RECORD 2 ]---+---------------------------
id              | 2
nombre          | Carlos
apellidos       | Ferrer Ibáñez
email           | [email protected]
ciudad          | Valencia
pais            | España
fecha_registro  | 2025-01-22
referido_por_id | 1

Cada fila pasa a ocupar un bloque vertical. Se ve perfectamente que el cliente 1 tiene referido_por_id vacío (es NULL: llegó por su cuenta) y el cliente 2 fue referido por el cliente 1.

\x alterna entre activado y desactivado; \x auto deja que psql decida según el ancho del terminal, y es la opción más cómoda para el día a día.

Otros metacomandos que te ahorrarán tiempo en este módulo:

Metacomando Para qué
\d productos Recordar los nombres exactos de las columnas
\x auto Modo expandido solo cuando haga falta
\timing on Ver cuánto tarda cada consulta (útil desde el módulo 8)
\e Editar la última consulta en tu editor de texto
\g Reejecutar la última consulta

  1. Primeras consultas reales sobre TiendaVerde

Cierra la lección practicando la proyección sobre las tres tablas que más usarás.

Quiénes son los clientes y de dónde son:

SELECT nombre,
       apellidos,
       ciudad,
       pais
FROM clientes;
nombre apellidos ciudad pais
Lucía Martínez Soler Valencia España
Carlos Ferrer Ibáñez Valencia España
Marta Sanchis Gil Castellón España
Javier Ortega Ruiz Madrid España
Ana Belmonte Roca Barcelona España
Pau Llorens Vidal Valencia España
Sofia Moreira Costa Lisboa Portugal
Tiago Almeida Nunes Oporto Portugal
Camille Dubois Lyon Francia
Julien Moreau París Francia
Elena Navarro Puig Alicante España
Diego Ramos Herrera Sevilla España
Núria Bosch Ferrer Barcelona España
Hugo Iglesias Pardo Zaragoza España
Inés Carrasco Vega Valencia España

El equipo, con su jerarquía en bruto:

SELECT id,
       nombre,
       apellidos,
       puesto,
       jefe_id
FROM empleados;
id nombre apellidos puesto jefe_id
1 Rosa Alcázar Vives Directora general (null)
2 Andrés Company Talens Responsable de ventas 1
3 Beatriz Nadal Ripoll Responsable de logística 1
4 Óscar Peris Blasco Comercial 2
5 Laia Puig Sanchis Comercial 2
6 Marc Estévez Roig Atención al cliente 2
7 Irene Salvador Mira Operaria de almacén 3
8 Daniel Vercher Lluch Analista de datos 1

Ahí está el NULL de Rosa Alcázar Vives, la única sin jefe. Verlo como una celda vacía es tu primer contacto práctico con los nulos: el módulo 4 les dedica una lección entera.

El estado de los pedidos:

SELECT id,
       cliente_id,
       fecha_pedido,
       estado
FROM pedidos;
id cliente_id fecha_pedido estado
1 1 2025-03-04 entregado
2 2 2025-03-12 entregado
3 3 2025-04-02 entregado
4 4 2025-04-19 entregado
5 1 2025-05-07 entregado
6 5 2025-05-23 cancelado
7 6 2025-06-11 entregado
8 7 2025-06-28 entregado
9 8 2025-07-15 entregado
10 9 2025-08-03 entregado
11 2 2025-09-09 entregado
12 10 2025-10-01 entregado
13 11 2025-10-22 entregado
14 12 2025-11-14 entregado
15 1 2025-12-02 entregado
16 4 2025-12-19 enviado
17 7 2026-01-13 enviado
18 5 2026-01-27 pagado
19 6 2026-02-09 pagado
20 9 2026-02-21 pendiente

Observa que cliente_id es un número, no un nombre. Para saber que el pedido 10 es de Camille Dubois hay que ir a clientes, y eso exige combinar dos tablas: es exactamente lo que hace el JOIN del módulo 3. Hasta entonces trabajaremos siempre con una tabla cada vez.

Errores Comunes y Consejos

  • Coma sobrante antes de FROM. SELECT id, nombre, FROM productossyntax error at or near "FROM". El error señala FROM, pero el fallo está en la coma anterior (regla del "mira el token anterior" de 01-03).
  • Coma que falta entre columnas. SELECT nombre precio FROM productos no da error: PostgreSQL entiende precio como alias de nombre y devuelve una sola columna llamada precio con los nombres de los productos. Es un fallo silencioso; cuenta siempre las columnas del resultado.
  • column "..." does not exist. Casi siempre es una errata o una columna que está en otra tabla. \d tabla antes de reescribir a ciegas.
  • Confundir comillas. SELECT "nombre" FROM productos funciona (identificador en minúsculas); SELECT 'nombre' FROM productos devuelve 20 filas con el texto literal nombre. No es lo mismo.
  • Suponer que el resultado viene ordenado. Funciona hoy con 20 filas y falla el día que la tabla tenga un millón. ORDER BY o nada.
  • Dejar SELECT * en el código. Úsalo para explorar, sustitúyelo por la lista de columnas en cuanto la consulta sea definitiva.
  • Consejo: mira siempre el recuento de filas. Si esperabas 20 y psql dice (0 filas), has aprendido algo antes de leer una sola celda.
  • Consejo: prueba las expresiones sin FROM. SELECT 3 * 12.50 * (1 - 0.05); valida el cálculo en un segundo, sin ruido de tablas.
  • Consejo: activa \x auto. Es la diferencia entre leer una fila de clientes y pelearse con el terminal.

Ejercicios

Ejercicio 1

Escribe una consulta que devuelva el nombre, el país y el email de todos los proveedores. Después responde: ¿cuántas filas devuelve y por qué no hace falta ningún filtro para obtenerlas todas?

Ejercicio 2

Sobre empleados, escribe una consulta que muestre las columnas en este orden exacto: puesto, apellidos, nombre, ciudad. Justifica por qué el resultado no coincide con el orden en que las columnas están definidas en la tabla.

Ejercicio 3

Sin usar ninguna tabla, calcula con SELECT estas tres cosas y explica el resultado:

  1. El precio con IVA (21 %) del producto 15 (Té verde matcha ceremonial, 22.00 €).
  2. El importe de la línea 27 de lineas_pedido: 8 unidades a 1.95 € con un descuento de 0.15.
  3. El margen bruto del producto 6 (precio 18.90, coste 9.50).

Soluciones

Solución 1

SELECT nombre,
       pais,
       email
FROM proveedores;
nombre pais email
Huerta del Turia España [email protected]
BioSierra Ibérica España [email protected]
Verde Atlántico Portugal [email protected]
Maison Nature Francia [email protected]
EcoNordic Supplies Alemania [email protected]

Devuelve 5 filas. Una consulta sin WHERE no descarta ninguna fila: el paso 2 del orden lógico simplemente no existe, así que todo lo que sale de FROM llega a SELECT. Fíjate en que el proveedor 5 aparece aunque tenga activo = FALSE: nada lo excluye. Filtrarlo será tarea de la lección 02-03.

Solución 2

SELECT puesto,
       apellidos,
       nombre,
       ciudad
FROM empleados;
puesto apellidos nombre ciudad
Directora general Alcázar Vives Rosa Valencia
Responsable de ventas Company Talens Andrés Valencia
Responsable de logística Nadal Ripoll Beatriz Valencia
Comercial Peris Blasco Óscar Valencia
Comercial Puig Sanchis Laia Castellón
Atención al cliente Estévez Roig Marc Valencia
Operaria de almacén Salvador Mira Irene Valencia
Analista de datos Vercher Lluch Daniel Valencia

El razonamiento: la tabla define las columnas como id, nombre, apellidos, puesto, jefe_id, salario, fecha_contratacion, ciudad, pero ese orden solo lo usa SELECT *. Cuando enumeras columnas, el orden del resultado es el de tu lista, porque la proyección construye una relación nueva con la forma que tú decides. Es la misma razón por la que puedes omitir columnas o repetirlas.

Solución 3

SELECT 22.00 * 1.21          AS iva_matcha,
       8 * 1.95 * (1 - 0.15) AS importe_linea_27,
       18.90 - 9.50          AS margen_producto_6;
iva_matcha importe_linea_27 margen_producto_6
26.6200 13.2600 9.40

Tres observaciones sobre el razonamiento:

  1. 22.00 * 1.21 da 26.6200, no 26.62. Al multiplicar dos NUMERIC, PostgreSQL suma las escalas: dos decimales por dos decimales dan cuatro. Para un informe habrá que redondear.
  2. El descuento es una fracción, así que 0.15 es el 15 % y el factor que se aplica es (1 - 0.15) = 0.85. Escribir 8 * 1.95 * 0.15 daría el descuento, no el importe. Es el error de interpretación más frecuente con esta base de datos.
  3. La resta 18.90 - 9.50 mantiene dos decimales, porque en la suma y la resta la escala del resultado es la mayor de las dos, no la suma.

(Sí, hemos usado AS antes de tiempo: sin él las tres columnas se llamarían ?column?. Es la primera cláusula de la lección siguiente.)

Conclusión

Ya sabes interrogar una tabla:

  • La consulta mínima es SELECT columnas FROM tabla;: FROM dice de dónde salen las filas y SELECT qué columnas se proyectan.
  • SELECT * sirve para explorar, pero en producción se sustituye por la lista explícita de columnas: contrato estable, menos tráfico y mejores planes de ejecución.
  • El orden de las columnas del resultado lo decides tú con tu lista, no la definición de la tabla.
  • SELECT sin FROM convierte a PostgreSQL en una calculadora para probar expresiones antes de integrarlas en una consulta.
  • El conjunto de resultados es una relación temporal de solo lectura y sin orden garantizado: si el orden importa, ORDER BY.
  • Conoces el orden lógico de ejecución FROM → WHERE → SELECT → ORDER BY → LIMIT, que iremos completando en cada lección de este módulo.
  • Sabes leer la salida de psql, contar filas y usar el modo expandido \x cuando las filas son anchas.

En la próxima lección, Alias, expresiones y columnas calculadas, dejarás de limitarte a devolver lo que hay guardado y empezarás a calcular: precios con IVA, márgenes, márgenes porcentuales y el importe de una línea de pedido, la expresión que te acompañará durante todo el curso. De paso pondrás nombre decente a esas columnas con AS y descubrirás, gracias al orden lógico que acabas de aprender, por qué un alias funciona en unos sitios y en otros no.

Curso de SQL

Módulo 1: Introducción a SQL

Módulo 2: Consultas básicas de SQL

Módulo 3: Trabajando con múltiples tablas

Módulo 4: Filtrado avanzado de datos

Módulo 5: Manipulación de datos

Módulo 6: Funciones avanzadas de SQL

Módulo 7: Subconsultas y consultas anidadas

Módulo 8: Índices y optimización de rendimiento

Módulo 9: Transacciones y concurrencia

Módulo 10: Temas avanzados

Módulo 11: SQL en la práctica

Módulo 12: Proyecto final

© Copyright 2026. Todos los derechos reservados