We will take the freedom to consider temporary tables, not only the temporary tables in MariaDB/MySQL, but alsoe the subqueries used as columns in a select clause or as tables in a from clause.
A temporary table is defined as a table that is created and used in a limited context, being a concrete sentence in SQL (in the generic case of subqueries) or a session or connection . The TEMPORARY tables, for example, are objects that automatically disappear when the user session is ended. A subquery is accessible only when the statement is being executed. In this lesson, we are only treating the subquery cases, the TEMPORARY tables does not need more explanation.
A query can be used as another column in any query, only with the precaution of giving it a name. Although MariaDB/MySQL does not need it, it seems convenient to use the AS reserved word to improve the understanding of the query.
Notice this two separate queries:
select max(rrp) from article
select max(price) from linped
The following statement generates a table with 2 columns from previous queries:
select
(select max(rrp) from article) as mart,
(select max(price) from linorder) as mlinped;
In this case the subqueries are used to perform operations over them and within the query over a set of rows:
select cod,rrp,
rrp/(select max(rrp) from article)*100 as tpcpvp,
rrp/(select max(price) from linorder)*100 as tpcimporte
from article
where rrp between 1000 and 1200;
The columns "tpcpvp" and "tpcimporte" calculate the ratio between the rrp of each item and the maximum rrp in ARTICLE, and the maximum price in LINORDER respectively. For example, for item A0003 its rrp is 1075.00 €, which is 16.67 % of the maximum rrp in ARTICLE, and 15.81 % of the most expensive price in LINPED.
In other words, the query first finds these maximum rrp and price values and then, for each item, it calculates the percentages based on its particular rrp.
What in another systems is known as PIVOT columns (in SQL Server, for example) consists on building columns from the information contained in the rows. Here, we will calculate how many orders/requests have been done in october, november and december of any year. Before, we will check the data we have:
First, let's check the data we have:
select month(date) mes, count(*)
from orrder
where month(date) in (10,11,12)
group by month(date);
What we want to achieve is that, starting from the previous result, the months are converted into columns. To do this:
select
(select count(*)
from orrder
where month(date) =10) as octubre,
(select count(*)
from orrder
where month(date) =11) as noviembre,
(select count(*)
from orrder
where month(date) =12) as diciembre;
Sometimes it is necessary to include a subquery as another table to link in other query in a upper order. Again, we must name this temporary table in order to be able to make a reference to it. In this case, we take profit of the previous query to use it in a more complex one:
select tv.cod,resolution,tdt,tpcpvp,tcpimporte
from tv,
(select cod,pvp,
pvp/(select max(rrp) from article)*100 as tpcpvp,
pvp/(select max(price) from linorder)*100 as tpcimporte
from article
where rrp between 1000 and 1200) as calc
where tv.cod=calc.cod;
Note that the query used in the previous section is incorporated here with the alias "calc", and works as a temporary table or result. In other words we define it in the from of a general query but it behaves like any other table that we have had to bind in the where.
Common Table Expressions (CTE) was introduced in the SQL standard relatively recently (SQL:1999) and is not supported by all systems, although the most important ones have been incorporating it.
Basically, we can understand them as a query that obtains a temporary result on which we can make subsequent queries.
WITH nameCTE as (select...)
select ... from nameCTE...
After the reserved word WITH we must give a name to the CTE and then, in parentheses, define the query that will act as a result or temporary table. The following query is the one that makes use of the CTE.
We make use of the previous query again, but making use of a CTE that we will call "calc".
WITH calc AS (
SELECT cod,rrp,
rrp/(SELECT MAX(rrp) FROM article)*100 AS tpcpvp,
rrp/(SELECT MAX(price) FROM linorder)*100 AS tpcimporte
FROM article
WHERE rrp BETWEEN 1000 AND 1200
)
SELECT tv.cod,resolution,dtt,tpcpvp,tpcimporte
FROM tv, calc
WHERE tv.cod=calc.cod;
Obviously, the result is the same, we have simply moved the "calc" subquery to a CTE.
Building on the previous example in which we pivoted the result of a query:
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
After defining the CTE we make a "query of queries", each column is a subquery on the CTE filtering by the corresponding month.
CTEs have other more sophisticated uses such as recursion, but here we will simply keep in mind that it is another way to simplify complex queries, based on making a first query on which to obtain the result we are really looking for.
We can also define temporary tables as such, that is, tables that are created at a given time because we need them for later queries, but are not limited to a single use. This table will be available as another table and we will be able to use it whenever we want. The only limitation is that it will disappear automatically when we close the session, when we disconnect.
We take advantage of the previous query to create a temporary table. Here it is defined from a query but these tables can be created like any other with the CREATE TABLE command. As this is the creation of an object, we must be in a database where we have the necessary permissions to do so.
CREATE TEMPORARY TABLE mytable
SELECT article, month(date) month, COUNT(*) times
FROM orrder p
JOIN linorder l ON (p.numOrder=l.numOrder)
WHERE month(date) in (10,11,12) AND article < 'A0031'
GROUP BY article, month(date);
Let's query the newly created table to check its contents
select * from mytable where article='A0023';
We do not have to worry about eliminating it since the system will do it when the connection is closed, or when it considers it appropriate.
Let's combine several of the techniques with subqueries shown here. Let's get how many times (we don't care in what quantity) each item is requested in October, November or December of any year.
We have limited the query to the first items (code less than A0031) to reduce the result we are going to work on.
select article, month(date) month, count(*) times
from orrder p join linorder l on (p.numOrder=l.numOrder)
where month(date) in (10,11,12) and article < 'A0031'
group by article,month(date);
With the same intention as before, we will convert the months into columns using subqueries. To do this, we will simplify the new query by leveraging the previous one as a CTE.
with mycte as (
select article, month(date) month, count(*) times
from orrder p join linorder l on (p.numOrder=l.numOrder)
where month(date) in (10,11,12) and article < 'A0031'
group by article,month(date)
)
select distinct article,
(select times from mycte where month = 10 and mycte.article=t2.article)
as octubre,
(select times from mycte where month = 11 and mycte.article=t2.article)
as noviembre,
(select times from mycte where month = 12 and mycte.article=t2.article)
as diciembre
from mycte as t2;
In the CTE there is the previously mentioned query, and the following query is used to format the result in columns by months. From the above result we see that item A0023 has been ordered once in October and once in November. By displaying it as columns get (A0023, 1, 1, null), the same information, but tabulated by month.
Notice that the second query renames "mycte" as "t2" in the from. Let's say it queries "mycte" and gets t2.article, i.e. it traverses "mycte" to retrieve the article codes resulting from that query. Article by article, the 3 subqueries that define the columns of each month filter again "mycte" looking for the row of that article and of a particular month. For this was to rename the CTE, in order to be able to link the subqueries to each article run.
We can say that "mycte" is used in 4 places in the general query, but we have only defined it once.
Obviously, when formatting in this way, there are certain months in which an item has not been ordered, hence the appearance of null values. If we do not like the aspect obtained, we can make use of the IFNULL() function.
with mycte as (
select article, month(date) month, count(*) times
from orrder p join linorder l on (p.numOrder=l.numOrder)
where month(date) in (10,11,12) and article < 'A0031'
group by article,month(date)
)
select distinct article,
ifnull((select times from mycte where month = 10 and mycte.article=t2.article),'')
as octubre,
ifnull((select times from mycte where month = 11 and mycte.article=t2.article),'')
as noviembre,
ifnull((select times from mycte where month = 12 and mycte.article=t2.article),'')
as diciembre
from mycte as t2;