esquema de la base de datos: esquema.txt
datos: datos.txt
Habremos pasado de puntillas sobre cómo calcular en SQL, como realizar operaciones aritméticas simples, al menos.
select 20*4/7 cálculo,
round(20*4/7,2) redondeado,
truncate(20*4/7,2) truncado,
truncate(20*4/7,0) parteEntera,
ceiling(20*4/7) enteroNoMenor;
Como solo queremos ver el resultado y no depende de los datos de ninguna tabla, orden select sin from y sin where. Más o menos se utilizan los operadores de siempre: sumar, restar, multiplicar y dividir. Y otros más, mira el manual, que para eso está.
Y después están las funciones, las que realizan cálculos más complejos o, como en nuestro ejemplo, redondean, truncan o nos devuelven el entero inmediatamente superior al argumento. Igual que antes, hay un montón de funciones que puedes consultar en la referencia de MariaDB.
Tan solo nos vamos a parar en el detalle de la precisión en decimales. Como la columna "cálculo" es nueva y temporal, es un resultado, no tiene un tipo de datos predefinido. Por eso, MariaDB lo muestra según entiende que debe hacerlo, con 4 decimales. Si queremos solo 2, para eso está la función round() o truncate(), u otras similares. A partir de ahora verás que utilizamos bastante el redondeo, es únicamente por una cuestión estética. Revisa la lección Tipos de Datos y lo que pasa cuando tratamos los datos con un tipo u otro.
De acuerdo, pero lo interesante es trabajar con los datos de una base de datos. Nosotros, ahora, seguiremos trabajando con Tienda, nuestro espectacular negocio en línea. Recordemos lo que ahora mismo tenemos en la tabla ARTICULO.
select cod,nombre,precio coste from articulo;
Fíjate que hemos renombrado la columna "precio" y la hemos denominado "coste". Esta columna en esta tabla nos informa de qué nos cuesta a nosotros adquirir cada artículo (1). Obviamente, de dónde ha salido este dato es algo que no nos interesa ahora mismo.
(1) En realidad, cuando explicamos el esquema de la BD Tienda fuimos ambiguos, simplemente hablamos de que los artículos tienen un precio, sin especificar más. Cierto que nuestra tienda solo vende los "empaquetados" y esos tienen —esta vez sí fuimos claros — su precio de venta al público (PVP). Para nosotros un "paquete" es uno o más artículos que agrupamos para su venta a nuestros clientes, no vendemos artículos "sueltos".
Nada más normal que los costes cambien. ¿Qué pasaría si nos suministraran nuestros artículos un 2% más caros?
select cod,nombre,precio coste,round(precio*1.02,2) nuevoCoste
from articulo;
Hemos añadido una columna calculada a la consulta anterior. Más o menos, podemos decir que MariaDB recorre cada fila, y utilizando el precio de cada fila —el coste de cada artículo— nos muestra ese precio un 2% por ciento mayor (precio*1.02). Finalmente, lo redondea para que solo se muestren 2 decimales. Vuelve a ser un ejemplo de un cálculo sencillo, ahora a partir de datos extraídos dinámicamente de una tabla en una base de datos.
Vamos a algo más práctico y habitual. Efectivamente, nuestro proveedor nos ha subido el coste del AOVE un 5%, y así lo queremos reflejar en nuestra base de datos.
Esto no es necesario, pero nos servirá para comparar los datos originales y los nuevos. Vamos a copiar la tabla ARTICULO.
create table zarticulo select * from articulo;
ZARTICULO es una tabla creada a partir de los datos actuales de la tabla ARTICULO. Puedes comprobar que las filas son las mismas.
select * from zarticulo;
update articulo
set precio=precio*1.05
where cod between '00008' and '00012';
Acabamos de modificar los precios de los artículos del '00008' al '00012', los aceites, subiéndoles ese 5% adicional (precio=precio*1.05).
Comprueba que solo esas filas han cambiado de precio valiéndote de la copia que realizamos antes.
select a.cod,z.precio antiguo,a.precio nuevo
from articulo a, zarticulo z
where a.cod=z.cod
and a.precio<>z.precio;
Utilizamos ARTICULO —la tabla en la que hemo modificado filas— y ZARTICULO, la copia de antes de la actualización de los aceites. Para poder comparar bien, que los códigos de artículo coincidan en una y otra tabla, y solo queremos ver los artículos que han cambiado de precio.
Vemos los códigos de artículo, su precio anterior y el coste actual.
Recuerda que EMPAQUETADO es lo que vendemos al público. Echemos un vistazo a esa tabla.
select c.empaquetado,e.nombre,e.pvp,c.articulo,c.cantidad,a.nombre, a.precio,a.unidad
from empaquetado e, contiene c, articulo a
where e.cod=c.empaquetado and c.articulo=a.cod
order by c.empaquetado,c.articulo;
Debemos recordar, igualmente, que cada "empaquetado" contiene uno o más artículos. La columna "pvp" es el precio al que lo vendemos. Si un "empaquetado" tiene varios artículos, verás la misma información repetida, tantas veces como artículos se agrupen en él.
Mira el codificado como "EB00010". ¿A qué precio lo vendemos?
select *
from empaquetado
where cod='EB00010';
El "empaquetado" "EB00010" se vende a 31.33€ pero, ¿qué artículos contiene?
select *
from empaquetado e, contiene c
where e.cod=c.empaquetado
and cod='EB00010';
Como no hemos filtrado las columnas, nos las muestra todas. La parte izquierda son filas de EMPAQUETADO y la derecha las de CONTIENE. Esta última tabla hace de puente entre EMPAQUETADO y ARTICULO, es una relación muchos a muchos entre ellas, qué artículos contiene cada empaquetado.
Aligerando la sintaxis SQL, la creación de las 3 tablas. Fíjate en la clave primaria de CONTIENE, compuesta por las dos claves ajenas.
articulo (
cod char(5),
nombre varchar(45),
precio decimal(5,2),
unidad char(1),
PRIMARY KEY (cod),
FOREIGN KEY (unidad) REFERENCES unidad (unidad)
)
empaquetado (
cod char(7),
nombre varchar(45),
pvp decimal(7,2),
especificaciones varchar(1024),
PRIMARY KEY (cod)
);
contiene (
empaquetado char(7),
articulo char(5),
cantidad decimal(5,2),
PRIMARY KEY (empaquetado,articulo),
FOREIGN KEY (articulo) REFERENCES articulo (cod),
FOREIGN KEY (empaquetado) REFERENCES empaquetado (cod)
)
En este caso, el empaquetado "EB00010", que vendemos a 31.33€ y lo llamamos "Degustación AOVE", contiene 3 artículos: el 00008, 00009 y 000011, una única unidad de cada uno.
Como decíamos, la parte izquierda parece "repetida", pero esto son tablas y SQL, así funciona la concatenación de filas en una consulta. En cualquier caso, ahí tenemos la información que pedíamos.
Ahora vamos a añadir lo que nos cuesta a nosotros cada artículo.
select *
from empaquetado e, contiene c, articulo a
where e.cod=c.empaquetado and c.articulo=a.cod
and e.cod='EB00010';
Ahora la consulta involucra a tres tablas, añadimos el enlace entre CONTIENE y ARTICULO, y resolvemos la ambigüedad que se produce con el nombre de columna "cod", que se utiliza tanto en EMPAQUETADO como en ARTICULO.
Estamos manteniendo todas las columnas de las tres tablas para insistir en la idea del join de SQL, la concatenación de filas de varias tablas por valores que son iguales en ellas. En realidad deberíamos evitar tanta redundancia eliminando columnas que, simplemente, repiten un dato.
select c.empaquetado,e.nombre,e.pvp,c.articulo,c.cantidad,a.precio
from empaquetado e, contiene c, articulo a
where e.cod=c.empaquetado and c.articulo=a.cod
and e.cod='EB00010';
Igual que antes, el empaquetado y su pvp, los 3 artículos que lo componen y, ahora, el precio de coste de cada uno.
Si quisieramos saber lo que ganamos al vende ese "empaquetado", ¿qué consulta habría que hacer? Fácil, partiendo de la consulta anterior, añadimos una columna en la que restamos al "pvp" el coste de nuestros artículos.
select e.pvp-a.precio margen,
c.empaquetado,e.nombre,e.pvp,c.articulo,c.cantidad,a.precio
from empaquetado e, contiene c, articulo a
where e.cod=c.empaquetado and c.articulo=a.cod
and e.cod='EB00010';
¡Cuidado! Esto es un error, o no es lo que buscábamos. El PVP de la "Degustación AOVE" es para el conjunto de los 3 artículos, no podemos restar al total el coste individual de cada artículo. Por otra parte, los datos son confusos ya de partida, tenemos 3 márgenes diferentes. ¿Cuál es el beneficio real? Ninguno de esos 3.
¿Qué nos cuesta el empaquetado EB00010?
La tabla que nos dice qué artículos, y en qué cantidad, tiene cada empaquetado es CONTIENE, y la que nos da el coste de cada artículo es ARTICULO. Necesitamos, pues, combinar esas dos tablas. No queremos nada más, ningún dato específico del empaquetado, no necesitamos esa tabla.
select c.cantidad,a.precio
from contiene c, articulo a
where c.articulo=a.cod
and c.empaquetado='EB00010';
No es información nueva, simplemente mostramos cantidades y coste de cada artículo, pero ahora el cálculo es evidente: la suma de los productos de cantidad por precio. Aquí podemos utilizar la función SUM(), sumar los resultados, de todas las filas, de la expresión cantidad*precio.
select sum(c.cantidad*a.precio) costeEB00010
from contiene c, articulo a
where c.articulo=a.cod
and c.empaquetado='EB00010';
Dicho de otro modo, MariaDB va realizando el cálculo en cada fila y lo va acumulando para, al final, dar el total, la suma: 26.37€.
Ya podemos calcular el margen de beneficios. El pvp del empaquetado "EB00010" menos lo que hemos obtenido.
select 'EB00010',31.33-26.37 margen,round((31.33-26.37)/31.33*100,1) '%margen';
Por hacerlo más aparente, la fila de resultado nos da el código de empaquetado —que hemos puesto "a mano", con una constante string—, la resta en euros de los dos valores que conocemos, y el porcentaje que eso supone sobre el precio de coste.
Más bonito todavía si hacemos uso de una función de texto de concatenación para que el porcentaje se vea mejor.
select 'EB00010',
31.33 pvp,
31.33-26.37 beneficio,
concat(round((31.33-26.37)/31.33*100,1),'%') margen;
¿No hay otra forma de hacerlo? Hemos tenido que "apuntar en un papel", como si dijéramos, los datos para después calcular casi a mano. Efectivamente, las funciones de agregados, de las cuales forma parte SUM(), hacen esta y otra operaciones directamente. Pero eso ya es para otra lección. De momento quédate con que puedes hacer operaciones aritméticas más o menos complejas en cada fila, y que puedes, entre otras cosas, obtener el total de esos cálculos.
Como curiosidad, sí hay una forma de hacerlo con lo que sabes hasta ahora. Si cada consulta devuelve una única columna y una única fila, es decir, un único valor, ¿por qué no restarlos?
select
(select pvp from empaquetado where cod='EB00010')
-
(select sum(c.cantidad*a.precio) costeEB00010
from contiene c, articulo a
where c.articulo=a.cod and c.empaquetado='EB00010'
)
as margen;
Esto también lo dejamos para otra ocasión.