Nos hemos tomado la libertad de considerar tablas temporales tanto las tablas temporary en MariaDB/MySQL, como las subconsultas utilizadas como columna en la select o como tabla en el from.
Se entiende por tabla temporal aquella que se crea y utiliza en un contexto limitado, bien sea una orden concreta de SQL —el caso genérico de las subconsultas— o una sesión o conexión. Las tablas TEMPORARY, por ejemplo, son objetos que desaparecen automáticamente cuando se cierra la sesión de usuario. Una subconsulta es accesible solo mientras se ejecuta una determinada orden. En esta lección solo vamos a tratar los casos de subconsultas, las tablas TEMPORARY no necesitan más explicación.
Una consulta puede utilizarse como una columna más de cualquier consulta, solo hay que tener la precaución de darle nombre. Aunque MariaDB/MySQL no lo necesita realmente, parece conveniente utilizar la palabra reservada AS para mejorar la comprensión de cada parte de la consulta.
Supongamos dos consultas independientes:
select max(pvp) from articulo
select max(importe) from linped
Obviamente, una consulta nada tiene que ver con la otra. Si por cualquier razón quisiéramos obtener estos dos resultados en una única tabla, no necesitamos consultas complejas, simplemente utilizarlas como columnas de una consulta general.
use tiendaonline;
select
(select max(pvp) from articulo) as mart,
(select max(importe) from linped) as mlinped;
En este caso se utilizan las subconsultas para realizar cálculos sobre dentro de otra consulta:
select cod,pvp,
pvp/(select max(pvp) from articulo)*100 as tpcpvp,
pvp/(select max(importe) from linped)*100 as tpcimporte
from articulo
where pvp between 1000 and 1200;
Las columnas "tpcpvp" y "tpcimporte" calculan la proporción entre el pvp de cada artículo y el máximo pvp en ARTICULO y el máximo importe en LINPD respectivamente. Por ejemplo, para el artículo A0003 su pvp es 1075.00 €, lo que es un 16.67 % del máximo pvp en ARTICULO y un 15.81 % del importe más caro en LINPED.
Por así decirlo, la consulta encuentra primero esos valores máximos de pvp e importe y, despues, para cada artículo va haciendo el cálculo de los porcentajes en base a su pvp particular.
También conocido como pivotar una tabla —del inglés pivot— consiste en construir columnas a partir de la información contenida en las filas. En algunos sistemas hay órdenes disponibles para esta acción en concreto, pero aquí vamos a ver la solución básica a partir de subconsultas.
Vamos a calcular cuántos pedidos se han realizado en los meses de octubre, noviembre y diciembre de cualquier año.
Antes, vamos a comprobar los datos de los que disponemos:
select month(fecha) mes, count(*)
from pedido
where month(fecha) in (10,11,12)
group by month(fecha);
Lo que queremos conseguir es que, partiendo del resultado anterior, los meses se conviertan en columnas. Para ello:
select
(select count(*)
from pedido
where month(fecha) =10) as octubre,
(select count(*)
from pedido
where month(fecha) =11) as noviembre,
(select count(*)
from pedido
where month(fecha) =12) as diciembre;
A veces es necesario incluir una subconsulta como una tabla más y enlazarla en otra consulta de orden superior. Nuevamente, hay que darle nombre a esa tabla temporal para poder hacer referencia a ella. En este caso, aprovechamos la consulta anterior para utilizar en otra más compleja:
select tv.cod,resolucion,tdt,tpcpvp,tpcimporte
from tv,
(select cod,pvp,
pvp/(select max(pvp) from articulo)*100 as tpcpvp,
pvp/(select max(importe) from linped)*100 as tpcimporte
from articulo
where pvp between 1000 and 1200) as calc
where tv.cod=calc.cod;
Observemos que la consulta utilizada en la sección anterior la hemos incorporado aquí con el alias "calc", y trabaja como una tabla o resultado temporal. En otras palabras la definimos en el from de una consulta general pero se comporta como cualquier otra tabla que hemos tenido que enlazar en el where.
Del inglés common table expressions (CTE), es una construcción introducida en el estándar SQL hace relativamente poco (SQL:1999) y no está soportado por todos los sistemas, aunque los más importantes la han ido incorporándo.
Básicamente, podemos entenderlas como una consulta que obtiene un resultado temporal sobre el que podemos hacer consultas posteriores.
WITH nombreCTE as (select...)
select ... from nombreCTE...
Tras la palabra reservada WITH debemos dar un nombre a la CTE para, a continuación, entre paréntesis, definir la consulta que actuará como resultado o tabla temporal. La siguiente consulta es la que hace uso de de la CTE.
Volvemos a hacer uso de la consulta anterior, pero haciendo uso de una CTE que llamaremos "calc".
WITH calc as (
select cod,pvp,
pvp/(select max(pvp) from articulo)*100 as tpcpvp,
pvp/(select max(importe) from linped)*100 as tpcimporte
from articulo
where pvp between 1000 and 1200
)
select tv.cod,resolucion,tdt,tpcpvp,tpcimporte
from tv, calc
where tv.cod=calc.cod;
Obviamente, el resultado es el mismo, simplemente hemos trasladado la subconsulta "calc" a una CTE.
Aprovechando el ejemplo previo en el que pivotábamos el resultado de una consulta:
with original as
(
select month(fecha) mes, count(*) cuantos
from pedido
where month(fecha) in (10,11,12)
group by month(fecha)
)
select
(select cuantos from original where mes=10) octubre,
(select cuantos from original where mes=11) noviembre,
(select cuantos from original where mes=12) diciembre
Después de definir la CTE hacemos una "consulta de consultas", cada columna es una subconsulta sobre la CTE filtrando por el mes correspondiente.
Las CTE tienen otros usos más sofisticados como la recursividad, pero aquí simplemente nos quedamos con que es otra forma de simplificar consultas complejas, en base a hacer una primera consulta sobre la que obtener el resultado que realmente buscamos.
También podemos definir tablas temporales como tales, es decir, tablas que se crean en un momento dado por que las necesitemos para hacer consultas posteriores, pero que no se limitan a un único uso. Esa tabla estará disponible como una tabla más y podremos utilizarla cuando queramos. La única limitación es que desaparecerá automáticamente al cerrar nosotros la sesión, al desconectarnos.
Aprovechamos la consulta anterior para crear una tabla temporal. Aquí se define a partir de una consulta pero estas tablas se pueden crear como cualquier otra con la orden CREATE TABLE. Como se trata de la creación de un objeto, debemos estar en una base de datos en la que tengamos los permisos necesarios para hacerlo.
CREATE TEMPORARY TABLE mitabla
select articulo, month(fecha) mes, count(*) veces
from pedido p join linped l on (p.numpedido=l.numpedido)
where month(fecha) in (10,11,12) and articulo < 'A0031'
group by articulo,month(fecha);
Consultemos la tabla recién creada para comprobar su contenido
select * from mitabla where articulo='A0023';
No nos tenemos que preocupar de eliminarla puesto que ya lo hará el propio sistema cuando se cierre la conexión —o cuando lo considere oportuno—.
Vamos a combinar varias de las técnicas con subconsultas aquí mostradas.
Obtengamos cuántas veces —no nos importa en qué cantidad— se solicita cada artículo en octubre, noviembre o diciembre de cualquier año.
Hemos limitado la consulta a los primeros artículos (código menor que A0031) para reducir el resultado sobre el que vamos a trabajar.
select articulo, month(fecha) mes, count(*) veces
from pedido p join linped l on (p.numpedido=l.numpedido)
where month(fecha) in (10,11,12) and articulo < 'A0031'
group by articulo,month(fecha);
Con la misma intención que antes, convertiremos los meses en columnas haciendo uso de subconsultas. Para ello, vamos a simplificar la nueva consulta aprovechando la anterior como una CTE
with micte as (
select articulo, month(fecha) mes, count(*) veces
from pedido p join linped l on (p.numpedido=l.numpedido)
where month(fecha) in (10,11,12) and articulo < 'A0031'
group by articulo,month(fecha)
)
select distinct articulo,
(select veces from micte where mes = 10 and micte.articulo=t2.articulo)
as octubre,
(select veces from micte where mes = 11 and micte.articulo=t2.articulo)
as noviembre,
(select veces from micte where mes = 12 and micte.articulo=t2.articulo)
as diciembre
from micte as t2;
En la CTE está la consulta anteriormente comentada, y se aprovecha en la siguiente consulta para formatear el resultado en columnas por meses. Del resultado anterior vemos que el artículo A0023 se ha pedido una vez en octubre y otra vez en noviembre. Al mostrarlo como columnas obtener (A0023, 1, 1, null), la misma información, pero tabulada por meses.
Fijémonos en que la segunda consulta renombra "micte" como "t2" en el from. Digamos que consulta "micte" y obtiene t2.articulo, es decir recorre "micte" para recuperar los códigos de artículo que resultan de esa consulta. Artículo a artículo, las 3 subconsultas que definen las columnas de cada mes filtran de nuevo "micte" buscando la fila de ese artículo y de un més en concreto. Para esto era renombrar la CTE, para poder enlazar las subconsultas a cada artículo recorrido.
Podemos decir que "micte" se usa en 4 lugares de la consulta general, pero solo la hemos definido una vez.
Evidentemente, al formatear de esta manera, hay ciertos meses en los que un articulo no se ha pedido, de ahí la aparición de valores nulos. Si no nos gusta el aspecto conseguido, podemos hacer uso de la función IFNULL().
with micte as (
select articulo, month(fecha) mes, count(*) veces
from pedido p join linped l on (p.numpedido=l.numpedido)
where month(fecha) in (10,11,12) and articulo < 'A0031'
group by articulo,month(fecha)
)
select distinct articulo,
ifnull((select veces from micte where mes = 10 and micte.articulo=t2.articulo),'')
as octubre,
ifnull((select veces from micte where mes = 11 and micte.articulo=t2.articulo),'')
as noviembre,
ifnull((select veces from micte where mes = 12 and micte.articulo=t2.articulo),'')
as diciembre
from micte as t2;