En la lección anterior devolvías datos tal como están guardados. Pero una base de datos no guarda el precio con IVA, ni el margen de un producto, ni el importe de una línea de pedido: guarda las piezas mínimas y el resto se calcula al consultarlo. En esta lección aprenderás a construir esas columnas calculadas con expresiones aritméticas y de texto, a ponerles un nombre legible con AS, y a entender —usando el orden lógico de ejecución de 02-01— por qué ese nombre se puede usar en unos sitios de la consulta y en otros no. Aquí aparece además la expresión más importante de todo el curso: cantidad * precio_unitario * (1 - descuento).

Contenido

  1. Alias de columna con AS
  2. Cuándo el alias necesita comillas dobles
  3. Alias de tabla
  4. Columnas calculadas: aritmética sobre columnas
  5. La expresión estrella: el importe de una línea de pedido
  6. Concatenación de texto: || y CONCAT
  7. ROUND para presentar dinero
  8. Por qué el alias no funciona en WHERE pero sí en ORDER BY
  9. Errores Comunes y Consejos
  10. Ejercicios
  11. Conclusión

  1. Alias de columna con AS

Un alias es el nombre que quieres que tenga una columna en el resultado. Se declara con AS:

SELECT nombre AS producto,
       precio AS pvp
FROM productos;

Antes de nada, comprobemos por qué hace falta. Sin alias, una expresión no tiene nombre:

SELECT nombre, precio * 1.21
FROM productos;
nombre ?column?
Aceite de oliva virgen extra 500 ml 15.1250
... ...

?column? es la forma que tiene PostgreSQL de decir "esto no tiene nombre". Un informe con esa cabecera es inservible, y un programa que acceda al resultado por nombre de columna no encontrará nada. Con alias:

SELECT nombre                AS producto,
       precio                AS precio_sin_iva,
       precio * 1.21         AS precio_con_iva
FROM productos;
producto precio_sin_iva precio_con_iva
Aceite de oliva virgen extra 500 ml 12.50 15.1250
Arroz integral ecológico 1 kg 3.90 4.7190
Miel de azahar cruda 500 g 9.75 11.7975
Pasta de espelta 500 g 2.80 3.3880
Tomate triturado ecológico 400 g 1.95 2.3595
Crema facial de aloe vera 50 ml 18.90 22.8690
Champú sólido de romero 80 g 8.40 10.1640
Aceite corporal de almendras 200 ml 14.25 17.2425
Bálsamo labial de caléndula 15 ml 4.60 5.5660
Detergente ecológico concentrado 1 L 11.20 13.5520

(10 primeras de 20 filas.)

La palabra AS es opcional para las columnas. Estas dos líneas son equivalentes:

SELECT precio * 1.21 AS precio_con_iva FROM productos;
SELECT precio * 1.21    precio_con_iva FROM productos;

Aun así, escríbela siempre. Sin AS, olvidar una coma convierte una columna en el alias de la anterior y la consulta se ejecuta sin error dando un resultado equivocado (lo viste en 01-03 y en 02-01):

SELECT nombre precio FROM productos;   -- una sola columna, llamada "precio",
                                       -- que contiene los NOMBRES de los productos
precio
Aceite de oliva virgen extra 500 ml
...

  1. Cuándo el alias necesita comillas dobles

Un alias es un identificador, así que sigue las reglas de 01-03: sin comillas se pliega a minúsculas y solo admite letras, dígitos y guiones bajos.

Alias ¿Necesita comillas? Resultado
AS precio_con_iva No Columna precio_con_iva
AS PrecioConIVA No, pero... La columna se llamará precioconiva (plegado a minúsculas)
AS "Precio con IVA" (tiene espacios) Columna Precio con IVA
AS "Margen %" (tiene %) Columna Margen %
AS order (order es palabra reservada) Error sin comillas
AS 'Precio' Nunca Error: las comillas simples son para cadenas
SELECT nombre           AS "Producto",
       precio           AS "Precio (€)",
       precio * 1.21    AS "Precio con IVA (€)"
FROM productos;
Producto Precio (€) Precio con IVA (€)
Té verde matcha ceremonial 30 g 22.00 26.6200
... ... ...

Este es el único uso legítimo de las comillas dobles en este curso: alias de presentación destinados a un informe o una exportación. Para cualquier alias que vayas a reutilizar dentro de la consulta, usa snake_case en minúsculas y ahórrate las comillas.

Nota de dialecto: MySQL y SQLite permiten además usar comillas invertidas (`Precio con IVA`) o comillas simples como delimitador de alias. PostgreSQL no: comillas dobles o nada. Escribe siempre la forma estándar.

  1. Alias de tabla

También las tablas admiten alias, y aquí AS es opcional igualmente:

SELECT p.id,
       p.nombre,
       p.precio
FROM productos AS p;

Con una sola tabla parece un capricho: p.nombre no aporta nada frente a nombre. Su valor aparece en el módulo 3, cuando una consulta combine varias tablas que tienen columnas con el mismo nombre:

-- Anticipo del módulo 3, NO lo ejecutes todavía
SELECT p.nombre AS producto,
       c.nombre AS categoria
FROM productos AS p
JOIN categorias AS c ON p.categoria_id = c.id;

Sin los alias p y c, nombre sería ambiguo y PostgreSQL respondería column reference "nombre" is ambiguous. De ahí que en SQL profesional casi todas las consultas lleven alias de tabla.

Convenciones útiles para los alias de tabla:

Práctica Ejemplo Comentario
Inicial o abreviatura corta productos AS p, lineas_pedido AS lp Lo más extendido
Nombre significativo si hay ambigüedad empleados AS jefe, clientes AS referidor Imprescindible al unir una tabla consigo misma (03-06)
Prefijar todas las columnas si hay alias p.nombre, p.precio Evita sorpresas al añadir tablas después
Alias de una sola letra críptica productos AS x Evitar: ilegible en consultas largas

Una diferencia que sorprende: si pones alias a la tabla, el nombre original deja de estar disponible. Esto falla:

SELECT productos.nombre
FROM productos AS p;
ERROR:  invalid reference to FROM-clause entry for table "productos"
HINT:  Perhaps you meant to reference the table alias "p".

  1. Columnas calculadas: aritmética sobre columnas

Una columna calculada es una expresión en la lista del SELECT. Se evalúa fila a fila, con los valores de esa fila.

4.1. Margen bruto

SELECT nombre,
       precio,
       coste,
       precio - coste AS margen
FROM productos;
nombre precio coste margen
Aceite de oliva virgen extra 500 ml 12.50 7.80 4.70
Arroz integral ecológico 1 kg 3.90 2.10 1.80
Miel de azahar cruda 500 g 9.75 5.40 4.35
Pasta de espelta 500 g 2.80 1.35 1.45
Tomate triturado ecológico 400 g 1.95 0.90 1.05
Crema facial de aloe vera 50 ml 18.90 9.50 9.40
Champú sólido de romero 80 g 8.40 3.60 4.80
Aceite corporal de almendras 200 ml 14.25 7.10 7.15
Bálsamo labial de caléndula 15 ml 4.60 1.80 2.80
Detergente ecológico concentrado 1 L 11.20 6.00 5.20

(10 primeras de 20 filas.)

En la resta la escala del resultado es la mayor de las dos, así que el margen sale con dos decimales limpios. En la multiplicación no será así.

4.2. Margen porcentual

SELECT nombre,
       precio,
       coste,
       (precio - coste) / precio * 100 AS margen_pct
FROM productos;
nombre precio coste margen_pct
Aceite de oliva virgen extra 500 ml 12.50 7.80 37.6000000000000000
Arroz integral ecológico 1 kg 3.90 2.10 46.1538461538461538
Miel de azahar cruda 500 g 9.75 5.40 44.6153846153846154
Pasta de espelta 500 g 2.80 1.35 51.7857142857142857
Tomate triturado ecológico 400 g 1.95 0.90 53.8461538461538462
Crema facial de aloe vera 50 ml 18.90 9.50 49.7354497354497354

(6 primeras de 20 filas.)

Tres cosas que aprender de aquí:

  1. Los paréntesis son obligatorios. Sin ellos, precio - coste / precio * 100 se evaluaría como precio - ((coste / precio) * 100), porque * y / tienen más prioridad que - (tabla de precedencia de 01-03). El resultado para el producto 1 sería 12.50 - 62.4 = -49.90. No da error: da un número absurdo.
  2. La división es decimal, no entera, porque precio es NUMERIC y no INTEGER. Si las columnas fueran enteras, (precio - coste) / precio daría 0 para todas las filas y luego 0 * 100 = 0. Es el fallo clásico al calcular porcentajes; se resuelve convirtiendo un operando a NUMERIC con CAST (módulo 6).
  3. La cola de decimales es fea. PostgreSQL calcula la división NUMERIC con precisión amplia. Para presentarlo hace falta ROUND (sección 7).

4.3. Otras expresiones útiles sobre una sola tabla

SELECT nombre,
       stock,
       precio,
       stock * precio AS valor_inventario_pvp,
       stock * coste  AS valor_inventario_coste
FROM productos;
nombre stock precio valor_inventario_pvp valor_inventario_coste
Aceite de oliva virgen extra 500 ml 120 12.50 1500.00 936.00
Arroz integral ecológico 1 kg 200 3.90 780.00 420.00
Miel de azahar cruda 500 g 80 9.75 780.00 432.00
Pasta de espelta 500 g 150 2.80 420.00 202.50
Tomate triturado ecológico 400 g 300 1.95 585.00 270.00
Velas de cera de soja (pack 2) 0 13.75 0.00 0.00

(Selección de filas de las 20.)

Aquí stock es INTEGER y precio es NUMERIC(10,2): al mezclar tipos, PostgreSQL promueve el entero a NUMERIC y el resultado conserva dos decimales.

Cuidado con la división entre cero. Si en lugar de stock * precio calcularas algo / stock, el producto 13 (stock 0) haría fallar la consulta entera con ERROR: division by zero. La protección se hace con NULLIF, que se ve en el módulo 6.

  1. La expresión estrella: el importe de una línea de pedido

En 01-06 quedó fijado que pedidos no tiene columna total y que el importe de una línea es:

cantidad * precio_unitario * (1 - descuento)

Esta expresión va a aparecer en prácticamente todos los módulos que quedan. Escribámosla por primera vez:

SELECT id,
       pedido_id,
       producto_id,
       cantidad,
       precio_unitario,
       descuento,
       cantidad * precio_unitario * (1 - descuento) AS importe
FROM lineas_pedido;
id pedido_id producto_id cantidad precio_unitario descuento importe
1 1 1 2 11.95 0.00 23.9000
2 1 2 3 3.90 0.00 11.7000
3 1 14 2 3.25 0.00 6.5000
4 2 6 1 17.50 0.00 17.5000
5 2 9 2 4.60 0.00 9.2000
6 3 5 6 1.95 0.10 10.5300
7 3 4 4 2.80 0.00 11.2000
8 3 2 2 3.90 0.00 7.8000
9 4 15 1 22.00 0.00 22.0000
10 4 3 1 9.75 0.00 9.7500
11 5 10 1 11.20 0.00 11.2000
12 5 11 2 5.50 0.00 11.0000

(12 primeras de 47 filas.)

Desmontemos la expresión, porque cada pieza importa:

Trozo Por qué está ahí
cantidad * Unidades vendidas de ese producto en ese pedido
precio_unitario El precio del momento de la venta, no el actual. Por eso la línea 1 usa 11.95 € y no los 12.50 € que hoy cuesta el producto 1
(1 - descuento) descuento es una fracción: 0.10 = 10 %. 1 - 0.10 = 0.90, o sea "se cobra el 90 %"
Los paréntesis Sin ellos, cantidad * precio_unitario * 1 - descuento restaría 0.10 € al total en lugar de aplicar un 10 %

Comprueba la línea 6 a mano: 6 unidades × 1.95 € = 11.70 €; con un 10 % de descuento, 11.70 × 0.90 = 10.53 €. Coincide.

Y fíjate en el cuatro decimales: 10.5300, no 10.53. La cantidad es entera y no aporta escala, precio_unitario aporta 2 y (1 - descuento) otros 2, así que el resultado sale con 4. Aritméticamente es correcto y para sumar es lo que quieres, pero para enseñárselo a alguien hay que redondear.

Seis líneas de las 47 llevan descuento; estos son sus importes exactos:

id pedido_id cantidad precio_unitario descuento importe
6 3 6 1.95 0.10 10.5300
18 8 3 12.50 0.05 35.6250
24 10 2 18.90 0.10 34.0200
27 11 8 1.95 0.15 13.2600
39 16 2 11.20 0.05 21.2800
45 19 6 4.95 0.10 26.7300

Ahí hay un detalle revelador: la línea 24 vale 34.0200 € y la devolución del pedido 10 registrada en devoluciones es de 34.02 €. Los datos son coherentes entre tablas, y comprobarlo con una consulta será uno de los ejercicios del módulo 3.

Error frecuente: escribir cantidad * precio_unitario * descuento. Eso calcula el importe descontado, no el importe cobrado. Para la línea 6 daría 1.17 € en lugar de 10.53 €.

  1. Concatenación de texto: || y CONCAT

El operador estándar de concatenación en SQL es ||:

SELECT nombre || ' ' || apellidos AS cliente,
       ciudad,
       pais
FROM clientes;
cliente 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

Puedes concatenar texto con números: PostgreSQL convierte el número a texto automáticamente.

SELECT nombre,
       'Ref. ' || id || ' - ' || precio || ' EUR' AS etiqueta
FROM productos;
nombre etiqueta
Aceite de oliva virgen extra 500 ml Ref. 1 - 12.50 EUR
Arroz integral ecológico 1 kg Ref. 2 - 3.90 EUR
Miel de azahar cruda 500 g Ref. 3 - 9.75 EUR

(3 primeras de 20 filas.)

6.1. La trampa de || con NULL

Concatenar cualquier cosa con NULL da NULL. Todo el texto se pierde. Esta es una de las sorpresas más caras de SQL, y TiendaVerde tiene datos para demostrarlo: siete clientes tienen referido_por_id a NULL.

SELECT nombre,
       referido_por_id,
       'Referido por el cliente ' || referido_por_id AS origen
FROM clientes;
nombre referido_por_id origen
Lucía (null) (null)
Carlos 1 Referido por el cliente 1
Marta 1 Referido por el cliente 1
Javier (null) (null)
Ana 2 Referido por el cliente 2
Pau (null) (null)
Sofia (null) (null)
Tiago 7 Referido por el cliente 7
Camille (null) (null)
Julien 9 Referido por el cliente 9
Elena 6 Referido por el cliente 6
Diego (null) (null)
Núria 5 Referido por el cliente 5
Hugo (null) (null)
Inés 1 Referido por el cliente 1

Siete filas con origen vacío, no siete filas con el texto y un hueco. El literal 'Referido por el cliente ' desaparece por completo, porque la regla de los nulos es que cualquier operación con un valor desconocido produce un valor desconocido.

6.2. CONCAT trata los nulos de otro modo

La función CONCAT ignora los NULL y los sustituye por cadena vacía:

SELECT nombre,
       referido_por_id,
       CONCAT('Referido por el cliente ', referido_por_id) AS origen
FROM clientes;
nombre referido_por_id origen
Lucía (null) Referido por el cliente
Carlos 1 Referido por el cliente 1
Javier (null) Referido por el cliente
Ana 2 Referido por el cliente 2

(Selección de filas de las 15.)

Ya no se pierde el texto, pero el resultado tampoco es correcto: dice "Referido por el cliente" sin decir cuál. La solución de verdad es sustituir el nulo por un texto sensato con COALESCE, que se estudia en la lección 06-04:

-- Anticipo del módulo 6
SELECT nombre,
       COALESCE('Referido por el cliente ' || referido_por_id, 'Llegó por su cuenta') AS origen
FROM clientes;

Comparativa para tener a mano:

| Aspecto | a || b | CONCAT(a, b) | |---|---|---| | Estándar SQL | Sí | No (pero muy extendida) | | Con NULL | Devuelve NULL | Trata el NULL como '' | | En MySQL | Por defecto no concatena: || es el OR lógico | Es la forma habitual | | En SQL Server | No existe; se usa + | Existe desde 2012 | | En SQLite | Sí | No existe |

Nota de dialecto importante: en MySQL, 'a' || 'b' devuelve 0, porque ahí || significa OR. Solo se comporta como concatenación si el servidor tiene activado el modo PIPES_AS_CONCAT. Si escribes SQL que deba funcionar en MySQL y PostgreSQL, usa CONCAT.

  1. ROUND para presentar dinero

ROUND(expresión, decimales) redondea a la cantidad de decimales que indiques:

SELECT nombre,
       precio,
       ROUND(precio * 1.21, 2)                   AS precio_con_iva,
       ROUND((precio - coste) / precio * 100, 2) AS margen_pct
FROM productos;
nombre precio precio_con_iva margen_pct
Aceite de oliva virgen extra 500 ml 12.50 15.13 37.60
Arroz integral ecológico 1 kg 3.90 4.72 46.15
Miel de azahar cruda 500 g 9.75 11.80 44.62
Pasta de espelta 500 g 2.80 3.39 51.79
Tomate triturado ecológico 400 g 1.95 2.36 53.85
Crema facial de aloe vera 50 ml 18.90 22.87 49.74
Champú sólido de romero 80 g 8.40 10.16 57.14
Aceite corporal de almendras 200 ml 14.25 17.24 50.18
Bálsamo labial de caléndula 15 ml 4.60 5.57 60.87
Detergente ecológico concentrado 1 L 11.20 13.55 46.43
Estropajo vegetal de luffa (pack 3) 5.50 6.66 60.00
Bolsas reutilizables de algodón (pack 5) 9.90 11.98 56.57
Velas de cera de soja (pack 2) 13.75 16.64 49.82
Infusión de manzanilla ecológica 20 uds 3.25 3.93 56.92
Té verde matcha ceremonial 30 g 22.00 26.62 43.18
Kombucha de jengibre 750 ml 4.95 5.99 53.54
Zumo de naranja prensado en frío 1 L 5.40 6.53 51.85
Cepillo de dientes de bambú 3.50 4.24 65.71
Desodorante natural en barra 50 g 7.80 9.44 57.69
Cápsulas de espirulina 120 uds 16.40 19.84 46.95

Ahora sí es una tabla presentable. Y con ella ya puedes responder a una pregunta de negocio: el producto con más margen porcentual es el cepillo de dientes de bambú (65.71 %), y el de menos, el aceite de oliva virgen extra (37.60 %) —el producto estrella del catálogo es, precisamente, el que menos margen relativo deja.

Lo mismo para el importe de línea:

SELECT id,
       pedido_id,
       ROUND(cantidad * precio_unitario * (1 - descuento), 2) AS importe
FROM lineas_pedido;
id pedido_id importe
6 3 10.53
18 8 35.63
24 10 34.02
27 11 13.26
39 16 21.28
45 19 26.73

(Las 6 líneas con descuento, de las 47.)

Observa la línea 18: el valor exacto es 35.6250 y ROUND lo deja en 35.63. Sobre tipos NUMERIC, PostgreSQL redondea "medio hacia arriba" (alejándose del cero), que es lo que espera cualquier contable.

Regla del curso: calcula con la expresión completa y redondea solo al final, para presentar. Si redondeas cada línea y luego sumas, el total puede diferir en céntimos del total redondeado. Las funciones numéricas (ROUND, CEIL, FLOOR, TRUNC, ABS) se estudian a fondo en la lección 06-02.

  1. Por qué el alias no funciona en WHERE pero sí en ORDER BY

Aquí es donde el orden lógico de 02-01 deja de ser teoría. Intenta filtrar por una columna calculada:

SELECT nombre,
       precio * 1.21 AS precio_con_iva
FROM productos
WHERE precio_con_iva > 20;
ERROR:  column "precio_con_iva" does not exist
LINE 4: WHERE precio_con_iva > 20;
              ^

La tabla existe, el alias está escrito justo arriba, y aun así el motor dice que esa columna no existe. La explicación está en el diagrama:

flowchart LR
    A["1 · FROM<br/>productos"] --> B["2 · WHERE<br/>❌ el alias aún NO existe"]
    B --> C["3 · SELECT<br/>✅ aquí NACE el alias"]
    C --> D["4 · ORDER BY<br/>✅ el alias ya existe"]
    D --> E["5 · LIMIT"]

Cuando se evalúa WHERE, el paso SELECT todavía no se ha ejecutado, así que el nombre precio_con_iva no se ha creado aún. Cuando se evalúa ORDER BY, sí.

Las dos soluciones para filtrar por una expresión:

-- Opción A: repetir la expresión en el WHERE (siempre funciona)
SELECT nombre,
       precio * 1.21 AS precio_con_iva
FROM productos
WHERE precio * 1.21 > 20;
nombre precio_con_iva
Crema facial de aloe vera 50 ml 22.8690
Té verde matcha ceremonial 30 g 26.6200
-- Opción B: envolver la consulta en una subconsulta (módulo 7)
SELECT nombre, precio_con_iva
FROM (SELECT nombre, precio * 1.21 AS precio_con_iva FROM productos) AS t
WHERE precio_con_iva > 20;

Y en ORDER BY el alias funciona sin más (lección 02-05):

SELECT nombre,
       precio - coste AS margen
FROM productos
ORDER BY margen DESC;
nombre margen
Té verde matcha ceremonial 30 g 9.50
Crema facial de aloe vera 50 ml 9.40
Cápsulas de espirulina 120 uds 7.70
Aceite corporal de almendras 200 ml 7.15
Velas de cera de soja (pack 2) 6.85

(5 primeras de 20 filas.)

Resumen para memorizar:

Cláusula ¿Se puede usar un alias de SELECT? Motivo
WHERE No Se ejecuta antes que SELECT
GROUP BY Sí en PostgreSQL (extensión), no en el estándar Módulo 4
HAVING No Módulo 4
ORDER BY Se ejecuta después de SELECT

Errores Comunes y Consejos

  • Olvidar AS y perder una coma. SELECT nombre precio FROM productos no falla: devuelve los nombres bajo la cabecera precio. Cuenta siempre las columnas del resultado.
  • Alias con comillas simples. AS 'Precio' es un error de sintaxis en PostgreSQL. Comillas dobles para identificadores, simples para cadenas.
  • Esperar que AS PrecioIVA conserve las mayúsculas. Sin comillas se pliega a precioiva.
  • Usar un alias en WHERE. column "..." does not exist. Repite la expresión o usa una subconsulta.
  • Olvidar los paréntesis en el margen porcentual. precio - coste / precio * 100 no da error y devuelve números sin sentido.
  • Confundir el descuento con el importe descontado. El importe cobrado es cantidad * precio_unitario * (1 - descuento); sin el 1 - calculas justo lo contrario.
  • Concatenar con || sobre columnas que pueden ser nulas. Pierdes toda la cadena. Usa CONCAT o, mejor, COALESCE (módulo 6).
  • Redondear demasiado pronto. Redondea solo en la capa de presentación; los cálculos intermedios, con toda la precisión.
  • Consejo: nombra los alias como columnas reales. snake_case, en minúsculas, descriptivos: precio_con_iva, margen_pct, importe. Los alias "bonitos" con comillas, solo para el informe final.
  • Consejo: valida la expresión con SELECT sin FROM. SELECT 8 * 1.95 * (1 - 0.15); te confirma en un segundo si la fórmula es la que crees.

Ejercicios

Ejercicio 1

Escribe una consulta sobre productos que devuelva, con alias legibles: el nombre del producto, su precio, su margen unitario en euros y su margen porcentual redondeado a un decimal. Explica por qué los paréntesis del margen porcentual son imprescindibles.

Ejercicio 2

Sobre empleados, construye una única columna de texto llamada ficha con este formato exacto: Apellidos, Nombre (Puesto) - Ciudad. Por ejemplo: Alcázar Vives, Rosa (Directora general) - Valencia.

Ejercicio 3

Sobre lineas_pedido, devuelve el id de la línea, el pedido al que pertenece, el importe bruto (sin aplicar descuento), el importe descontado en euros y el importe final cobrado, los tres redondeados a dos decimales. Comprueba tus resultados con las seis líneas que tienen descuento.

Soluciones

Solución 1

SELECT nombre                                     AS producto,
       precio,
       precio - coste                             AS margen_eur,
       ROUND((precio - coste) / precio * 100, 1)  AS margen_pct
FROM productos;
producto precio margen_eur margen_pct
Aceite de oliva virgen extra 500 ml 12.50 4.70 37.6
Arroz integral ecológico 1 kg 3.90 1.80 46.2
Miel de azahar cruda 500 g 9.75 4.35 44.6
Pasta de espelta 500 g 2.80 1.45 51.8
Tomate triturado ecológico 400 g 1.95 1.05 53.8
Crema facial de aloe vera 50 ml 18.90 9.40 49.7
Champú sólido de romero 80 g 8.40 4.80 57.1
Aceite corporal de almendras 200 ml 14.25 7.15 50.2
Bálsamo labial de caléndula 15 ml 4.60 2.80 60.9
Detergente ecológico concentrado 1 L 11.20 5.20 46.4

(10 primeras de 20 filas.)

Razonamiento sobre los paréntesis. La precedencia de operadores de 01-03 dice que * y / se evalúan antes que -. Sin paréntesis, precio - coste / precio * 100 significa precio - ((coste / precio) * 100). Para el producto 1: 12.50 - ((7.80/12.50) * 100) = 12.50 - 62.40 = -49.90. Un margen negativo del -49.90 en un producto que gana 4.70 € por unidad. La consulta se ejecuta sin ningún aviso: es un error silencioso, el peor tipo.

Solución 2

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

Razonamiento. Se alternan columnas y literales de texto: los literales van entre comillas simples y llevan dentro los espacios, comas y paréntesis del formato. Han salido las 8 filas completas porque en esta tabla ninguna de las cuatro columnas usadas es nula. Si ciudad lo fuera —y podría serlo, porque la columna admite nulos—, esa fila devolvería NULL entera. En una consulta destinada a producción convendría blindarla con COALESCE(ciudad, 'Sin asignar'), que verás en 06-04.

Solución 3

SELECT id,
       pedido_id,
       ROUND(cantidad * precio_unitario, 2)                     AS importe_bruto,
       ROUND(cantidad * precio_unitario * descuento, 2)         AS descuento_eur,
       ROUND(cantidad * precio_unitario * (1 - descuento), 2)   AS importe_final
FROM lineas_pedido;

Las seis líneas con descuento, de las 47 que devuelve la consulta:

id pedido_id importe_bruto descuento_eur importe_final
6 3 11.70 1.17 10.53
18 8 37.50 1.88 35.63
24 10 37.80 3.78 34.02
27 11 15.60 2.34 13.26
39 16 22.40 1.12 21.28
45 19 29.70 2.97 26.73

Razonamiento. Las tres columnas comparten la base cantidad * precio_unitario; lo que cambia es qué se hace con descuento: multiplicar por descuento da lo que el cliente se ahorra, multiplicar por (1 - descuento) da lo que paga. La comprobación de que la fórmula es correcta es que descuento_eur + importe_final = importe_bruto en todas las filas. En la línea 18 hay una diferencia de un céntimo aparente (1.88 + 35.63 = 37.51 ≠ 37.50): es el efecto de redondear cada columna por separado, ya que los valores exactos son 1.8750 y 35.6250. Es exactamente el motivo por el que se redondea al final y no en cada paso intermedio.

Conclusión

Ahora tus consultas no solo leen: calculan.

  • Un alias con AS da nombre a cualquier columna o expresión; sin él verás ?column?. Escribe AS siempre, aunque sea opcional.
  • Las comillas dobles en un alias solo son necesarias si contiene espacios, símbolos o mayúsculas que quieras conservar: úsalas únicamente para alias de presentación.
  • Los alias de tabla parecen superfluos con una sola tabla y serán obligatorios en el módulo 3.
  • Sabes construir columnas calculadas: precio con IVA, margen en euros y margen porcentual, con los paréntesis en su sitio y sin caer en la división entera.
  • Dominas la expresión que sostiene todo el curso: cantidad * precio_unitario * (1 - descuento), y entiendes por qué el descuento es una fracción y por qué el resultado sale con cuatro decimales.
  • Concatenas texto con || y conoces su trampa: cualquier operando NULL anula toda la cadena. CONCAT la evita, COALESCE (módulo 6) la resuelve bien.
  • Presentas importes con ROUND(expresión, 2), redondeando solo al final.
  • Y, sobre todo, entiendes por el orden lógico de ejecución por qué un alias no existe todavía en WHERE y sí en ORDER BY.

En la siguiente lección, Filtrando datos con WHERE, añadirás el paso 2 del diagrama: dejarás de traer las 20 filas de productos o las 47 de lineas_pedido y empezarás a pedir solo las que cumplen una condición. Con comparaciones sobre números, texto y fechas, y con AND, OR, NOT y unos paréntesis que, otra vez, marcarán la diferencia entre un resultado correcto y uno silenciosamente equivocado.

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