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
- Alias de columna con
AS - Cuándo el alias necesita comillas dobles
- Alias de tabla
- Columnas calculadas: aritmética sobre columnas
- La expresión estrella: el importe de una línea de pedido
- Concatenación de texto:
||yCONCAT ROUNDpara presentar dinero- Por qué el alias no funciona en
WHEREpero sí enORDER BY - Errores Comunes y Consejos
- Ejercicios
- Conclusión
- Alias de columna con
AS
ASUn alias es el nombre que quieres que tenga una columna en el resultado. Se declara con AS:
Antes de nada, comprobemos por qué hace falta. Sin alias, una expresión no tiene nombre:
| 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 |
| ... |
- 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" |
Sí (tiene espacios) | Columna Precio con IVA |
AS "Margen %" |
Sí (tiene %) |
Columna Margen % |
AS order |
Sí (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.
- Alias de tabla
También las tablas admiten alias, y aquí AS es opcional igualmente:
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:
ERROR: invalid reference to FROM-clause entry for table "productos" HINT: Perhaps you meant to reference the table alias "p".
- 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
| 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
| 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í:
- Los paréntesis son obligatorios. Sin ellos,
precio - coste / precio * 100se evaluaría comoprecio - ((coste / precio) * 100), porque*y/tienen más prioridad que-(tabla de precedencia de 01-03). El resultado para el producto 1 sería12.50 - 62.4 = -49.90. No da error: da un número absurdo. - La división es decimal, no entera, porque
precioesNUMERICy noINTEGER. Si las columnas fueran enteras,(precio - coste) / preciodaría0para todas las filas y luego0 * 100 = 0. Es el fallo clásico al calcular porcentajes; se resuelve convirtiendo un operando aNUMERICconCAST(módulo 6). - La cola de decimales es fea. PostgreSQL calcula la división
NUMERICcon precisión amplia. Para presentarlo hace faltaROUND(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 * preciocalcularasalgo / stock, el producto 13 (stock 0) haría fallar la consulta entera conERROR: division by zero. La protección se hace conNULLIF, que se ve en el módulo 6.
- 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:
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 €.
- Concatenación de texto:
|| y CONCAT
|| y CONCATEl operador estándar de concatenación en SQL es ||:
| 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.
| 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'devuelve0, porque ahí||significaOR. Solo se comporta como concatenación si el servidor tiene activado el modoPIPES_AS_CONCAT. Si escribes SQL que deba funcionar en MySQL y PostgreSQL, usaCONCAT.
ROUND para presentar dinero
ROUND para presentar dineroROUND(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.
- Por qué el alias no funciona en
WHERE pero sí en ORDER BY
WHERE pero sí en ORDER BYAquí es donde el orden lógico de 02-01 deja de ser teoría. Intenta filtrar por una columna calculada:
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):
| 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 |
Sí | Se ejecuta después de SELECT |
Errores Comunes y Consejos
- Olvidar
ASy perder una coma.SELECT nombre precio FROM productosno falla: devuelve los nombres bajo la cabeceraprecio. 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 PrecioIVAconserve las mayúsculas. Sin comillas se pliega aprecioiva. - 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 * 100no 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 el1 -calculas justo lo contrario. - Concatenar con
||sobre columnas que pueden ser nulas. Pierdes toda la cadena. UsaCONCATo, 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
SELECTsinFROM.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
| 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
ASda nombre a cualquier columna o expresión; sin él verás?column?. EscribeASsiempre, 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 operandoNULLanula toda la cadena.CONCATla 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
WHEREy sí enORDER 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
- ¿Qué es SQL?
- Configurando tu entorno SQL
- Sintaxis básica de SQL
- Entendiendo bases de datos y tablas
- El modelo relacional: claves primarias y foráneas
- La base de datos del curso: TiendaVerde
Módulo 2: Consultas básicas de SQL
- Instrucción SELECT
- Alias, expresiones y columnas calculadas
- Filtrando datos con WHERE
- DISTINCT y eliminación de duplicados
- Ordenando datos con ORDER BY
- Limitando resultados con LIMIT
Módulo 3: Trabajando con múltiples tablas
- Operaciones JOIN
- INNER JOIN
- LEFT JOIN
- RIGHT JOIN
- FULL OUTER JOIN
- SELF JOIN y CROSS JOIN
- Uniones de conjuntos: UNION, INTERSECT y EXCEPT
Módulo 4: Filtrado avanzado de datos
- Usando LIKE para coincidencia de patrones
- Operadores IN y BETWEEN
- Valores NULL y IS NULL
- Funciones de agregación: COUNT, SUM, AVG, MIN y MAX
- Agregando datos con GROUP BY
- Cláusula HAVING
Módulo 5: Manipulación de datos
- Creando tablas y restricciones con CREATE TABLE
- Instrucción INSERT
- Instrucción UPDATE
- Instrucción DELETE
- Instrucción UPSERT (MERGE)
- Modificando el esquema: ALTER TABLE y migraciones seguras
Módulo 6: Funciones avanzadas de SQL
- Funciones de cadena
- Funciones numéricas
- Funciones de fecha y hora
- Conversión de tipos y manejo de NULL: CAST y COALESCE
- Expresiones condicionales
Módulo 7: Subconsultas y consultas anidadas
- Introducción a subconsultas
- Subconsultas correlacionadas
- EXISTS y NOT EXISTS
- Usando subconsultas en cláusulas SELECT, FROM y WHERE
- Subconsultas o JOIN: cuál elegir
Módulo 8: Índices y optimización de rendimiento
- Entendiendo los índices
- Creación y gestión de índices
- Tipos de índice y cuándo no indexar
- Técnicas de optimización de consultas
- Análisis del rendimiento de consultas
Módulo 9: Transacciones y concurrencia
- Introducción a las transacciones
- Propiedades ACID
- Instrucciones de control de transacciones
- Niveles de aislamiento y anomalías de concurrencia
- Manejo de concurrencia: bloqueos e interbloqueos
Módulo 10: Temas avanzados
- Vistas
- Expresiones de tabla comunes (CTE)
- Funciones de ventana
- Procedimientos almacenados
- Triggers
- JSON y datos semiestructurados
Módulo 11: SQL en la práctica
- Casos de uso en el mundo real
- Mejores prácticas
- Seguridad: inyección SQL, permisos y roles
- SQL para análisis de datos
- SQL en desarrollo web
