Repaso Express Consultas con MySQL SQL (Structured Query Language) • Es un lenguaje UNIVERSAL para “hablar” con bases de datos relacionales. • Nos permite armar complejos reportes con los datos de las tablas contenidas en la base. • Existen múltiples sistemas de gestión de bases de datos, como MySQL, MS SQL Server, PostgreSQL, Oracle, Snowflake, entre otros. Contents 1 Estructura de un query 2 Window Functions 3 Subqueries, Common Table Expressions (CTEs) & UNIONS 3 U2 – Tema 1 Estructura de un query Estructura básica de un query de SQL Calculemos el número, monto y el ticket promedio, de las transacciones PAGADAS en el mes de Junio 2022, agrupado por sexo y tipo de cuenta. Mostrar sólo los registros que tengan más de 20k transacciones y ordenar en forma descendente por monto de TPV. SELECT cl.sexo, cu.tipo, COUNT(transaccion_id) AS txn, SUM(t.monto) AS tpv, SUM(t.monto) / COUNT(transaccion_id) AS tkt_promedio FROM transacciones t INNER JOIN cuentas cu ON cu.cuenta_id = t.cuenta_id INNER JOIN clientes cl ON cl.cliente_id = cu.cliente_id WHERE t.estatus = 'PAGADA' AND t.fecha_transaccion_utc >= '2022-06-01' AND t.fecha_transaccion_utc < '2022-07-01' GROUP BY cl.sexo , cu.tipo HAVING COUNT(transaccion_id) > 20000 ORDER BY tpv DESC; 5 SELECT • Sentencia inicial de todo query en SQL. • Se usa para SELECCIONAR las columnas de la “tabla fuente” que serán desplegadas en el output del query. • Para crear columnas, podemos: • realizar operaciones entre columnas. usar funciones con sus valores. • Referencias sobre funciones • SELECT cl.sexo, cu.tipo, COUNT(transaccion_id) AS txn, SUM(t.monto) AS tpv, SUM(t.monto) / COUNT(transaccion_id) AS tkt_promedio FROM transacciones t INNER JOIN cuentas cu ON cu.cuenta_id = t.cuenta_id INNER JOIN clientes cl ON cl.cliente_id = cu.cliente_id WHERE t.estatus = 'PAGADA' AND t.fecha_transaccion_utc >= '2022-06-01' AND t.fecha_transaccion_utc < '2022-07-01' GROUP BY cl.sexo , cu.tipo HAVING COUNT(transaccion_id) > 20000 ORDER BY tpv DESC; 6 FROM • Se usa para especificar la tabla en donde se almacenan los datos de las columnas que serán desplegadas en el SELECT. • Es buena práctica identificar con un ALIAS a las tablas del FROM. • A la tabla generada en el join le diremos “tabla fuente”. SELECT cl.sexo, cu.tipo, COUNT(transaccion_id) AS txn, SUM(t.monto) AS tpv, SUM(t.monto) / COUNT(transaccion_id) AS tkt_promedio FROM transacciones t INNER JOIN cuentas cu ON cu.cuenta_id = t.cuenta_id INNER JOIN clientes cl ON cl.cliente_id = cu.cliente_id WHERE t.estatus = 'PAGADA' AND t.fecha_transaccion_utc >= '2022-06-01' AND t.fecha_transaccion_utc < '2022-07-01' GROUP BY cl.sexo , cu.tipo HAVING COUNT(transaccion_id) > 20000 ORDER BY tpv DESC; 7 JOIN • Parte opcional del FROM. • Nos permite unir dos o más tablas para acceder a sus datos desde un solo query. La “tabla fuente” se crea con TODOS los registros que satisfagan la condición del ON. • Es decir, con el join se “crea” internamente una nueva tabla. SELECT cl.sexo, cu.tipo, COUNT(transaccion_id) AS txn, SUM(t.monto) AS tpv, SUM(t.monto) / COUNT(transaccion_id) AS tkt_promedio FROM transacciones t INNER JOIN cuentas cu ON cu.cuenta_id = t.cuenta_id INNER JOIN clientes cl ON cl.cliente_id = cu.cliente_id WHERE t.estatus = 'PAGADA' AND t.fecha_transaccion_utc >= '2022-06-01' AND t.fecha_transaccion_utc < '2022-07-01' GROUP BY cl.sexo , cu.tipo HAVING COUNT(transaccion_id) > 20000 ORDER BY tpv DESC; 8 Tipos de Joins en MySQL Los JOINS se clasifican de acuerdo a los registros que se mantienen en la “tabla fuente” dependiendo de si se cumple la condición en el ON. INNER RIGHT / LEFT CROSS Mantiene los registros que satisfacen la condición. Toma una tabla como pivote, y agrega los registros de la otra que satisfagan la condición. Inserta NULLs cuando no hay coincidencias. Realiza TODAS las combinaciones posibles entre los registros de ambas tablas. No es necesario el ON. Link para más detalle sobre los JOINs. 9 WHERE • Con esta sentencia filtramos a la “tabla fuente”. • Es decir, en el WHERE establecemos las condiciones para mantener o no un registro en nuestra “tabla fuente”. SELECT cl.sexo, cu.tipo, COUNT(transaccion_id) AS txn, SUM(t.monto) AS tpv, SUM(t.monto) / COUNT(transaccion_id) AS tkt_promedio FROM transacciones t INNER JOIN cuentas cu ON cu.cuenta_id = t.cuenta_id INNER JOIN clientes cl ON cl.cliente_id = cu.cliente_id WHERE t.estatus = 'PAGADA' AND t.fecha_transaccion_utc >= '2022-06-01' AND t.fecha_transaccion_utc < '2022-07-01' GROUP BY cl.sexo , cu.tipo HAVING COUNT(transaccion_id) > 20000 ORDER BY tpv DESC; 10 GROUP BY • La sentencia GROUP BY indica los grupos sobre los que se realizarán las operaciones del SELECT. • Cada grupo está formado por las combinaciones de valores de las columnas indicadas en el GROUP BY. • El output final del query tendrá a lo más un registro por cada combinación de valores. SELECT cl.sexo, cu.tipo, COUNT(transaccion_id) AS txn, SUM(t.monto) AS tpv, SUM(t.monto) / COUNT(transaccion_id) AS tkt_promedio FROM transacciones t INNER JOIN cuentas cu ON cu.cuenta_id = t.cuenta_id INNER JOIN clientes cl ON cl.cliente_id = cu.cliente_id WHERE t.estatus = 'PAGADA' AND t.fecha_transaccion_utc >= '2022-06-01' AND t.fecha_transaccion_utc < '2022-07-01' GROUP BY cl.sexo , cu.tipo HAVING COUNT(transaccion_id) > 20000 ORDER BY tpv DESC; 11 HAVING • Es un segundo filtro aplicado sobre el resultado tras aplicar el GROUP BY y el SELECT. • A diferencia del WHERE, el filtro se puede aplicar sobre el resultado de una función de agregación, por ejemplo de un SUM o un COUNT.. SELECT cl.sexo, cu.tipo, COUNT(transaccion_id) AS txn, SUM(t.monto) AS tpv, SUM(t.monto) / COUNT(transaccion_id) AS tkt_promedio FROM transacciones t INNER JOIN cuentas cu ON cu.cuenta_id = t.cuenta_id INNER JOIN clientes cl ON cl.cliente_id = cu.cliente_id WHERE t.estatus = 'PAGADA' AND t.fecha_transaccion_utc >= '2022-06-01' AND t.fecha_transaccion_utc < '2022-07-01' GROUP BY cl.sexo , cu.tipo HAVING COUNT(transaccion_id) > 20000 ORDER BY tpv DESC; 12 ORDER BY • El ORDER BY se usa para ordenar el output final del query. • Para el ordenamiento se puede usar cualquiera de las columnas generadas con el SELECT. • Cuando se tienen múltiples columnas en el ORDER BY, el ordenamiento se aplica de izquierda a derecha. SELECT cl.sexo, cu.tipo, COUNT(transaccion_id) AS txn, SUM(t.monto) AS tpv, SUM(t.monto) / COUNT(transaccion_id) AS tkt_promedio FROM transacciones t INNER JOIN cuentas cu ON cu.cuenta_id = t.cuenta_id INNER JOIN clientes cl ON cl.cliente_id = cu.cliente_id WHERE t.estatus = 'PAGADA' AND t.fecha_transaccion_utc >= '2022-06-01' AND t.fecha_transaccion_utc < '2022-07-01' GROUP BY cl.sexo , cu.tipo HAVING COUNT(transaccion_id) > 20000 ORDER BY tpv DESC; 13 En resumen… ¿Qué columnas necesito? ¿De dónde tomaré la info? ¿Qué filtros aplicaré a la info? ¿Cómo agruparé la info? ¿Con qué registros me quiero quedar? ¿En qué orden veré mi tabla? SELECT cl.sexo, cu.tipo, COUNT(transaccion_id) AS txn, SUM(t.monto) AS tpv, SUM(t.monto) / COUNT(transaccion_id) AS tkt_promedio FROM transacciones t INNER JOIN cuentas cu ON cu.cuenta_id = t.cuenta_id INNER JOIN clientes cl ON cl.cliente_id = cu.cliente_id WHERE t.estatus = 'PAGADA' AND t.fecha_transaccion_utc >= '2022-06-01' AND t.fecha_transaccion_utc < '2022-07-01’ GROUP BY cl.sexo , cu.tipo HAVING COUNT(transaccion_id) > 20000 ORDER BY tpv DESC; ¿Sabías que el orden en que se ejecuta el query no es el mismo con el que se escribe? Orden en que se escribe 1. 2. 3. 4. 5. 6. SELECT, AGGREGATE FUNCTIONS FROM, JOIN WHERE GROUP BY HAVING ORDER BY Orden en que se ejecuta 1. 2. 3. 4. 5. 6. 7. FROM, JOIN WHERE GROUP BY AGGREGATE FUNCTIONS HAVING SELECT ORDER BY 15 Y listo…ahora ya sabes lo básico de SQL!!! Referencias 1. FUNCIONES Y OPERADORES I. Operadores II. Funciones para controlar el flujo. III. Funciones matemáticas y aritméticas. IV. Funciones de fechas y Horas V. Funciones para cadenas de texto VI. Conversión de tipos de datos VII. Funciones agregadoras (sólo las básicas) VIII. Window Functions **MUY IMPORTANTES (Y ÚTILES) 2. Cheat Sheet de funciones de MySQL (ALTAMENTE RECOMENDABLE). 3. Ejercicios de Práctica ONLINE (ALTAMENTE RECOMENDABLE). 4. LIBRO SQL Practice Problems (ALTAMENTE RECOMENDABLE). U2 – Tema 2 Window Functions Introducción Para entender a las Window Functions, comencemos analizando a las Aggregate Functions: Dada la siguiente tabla: select cliente_id , sexo from banmaya.clientes where cliente_id <= 100004; ¿Puedes construir un query que calcule esta otra? select sexo , count(cliente_id) as num_clientes from banmaya.clientes where cliente_id <= 100004 group by 1; Introducción Para entender a las Window Functions, comencemos analizando a las Aggregate Functions: Aggregate Functions COUNT, SUM + GROUP BY < Hacen cálculos por grupos y reducen registros ¿Cómo puedo agregar los porcentajes de hombres y mujeres como una nueva columna? select sexo , count(cliente_id) as num_clientes , count(cliente_id) / sum(count(cliente_id)) over () Window Functions as num_clientes from banmaya.clientes COUNT, SUM + Window frame where cliente_id <= 100004 group by 1; Hacen cálculos por grupos y NO reducen registros Aggregate vs Window Functions • “Agregan” o reducen registros haciendo un cálculo por grupos. • Se retornan los valores únicos de cada grupo y el cálculo agregado. • Las usamos cuando se necesita mostrar la información agrupada. • Asigna un valor que es calculado con base a registros que guardan una relación. No reducen registros. • No modifican la estructura original de la tabla, y sólo agrega la columna con los cálculos. • Las usamos para hacer operaciones entre valores de distintas filas sin modificar la tabla original. Estructura Query para construir la tabla select transaccion_id , estatus , date_format(fecha_transaccion_utc, '%Y-%m-%d') fecha , count(transaccion_id) over ( partition by estatus order by day(fecha_transaccion_utc) rows between 2 preceding and 1 following ) as with_rows from banmaya.transacciones where cuenta_id = 200003 and fecha_transaccion_utc < '2021-05-10'; Estructura window_function() over ( partition by <…> order by <…> <window frame> ) as alias_columnas Estructura Window Function Query para construir la tabla select transaccion_id , estatus , date_format(fecha_transaccion_utc, '%Y-%m-%d') fecha , count(transaccion_id) over ( partition by estatus order by day(fecha_transaccion_utc) rows between 2 preceding and 1 following ) as with_rows from banmaya.transacciones where cuenta_id = 200003 and fecha_transaccion_utc < '2021-05-10'; • • • • • • • Window Function • Función que se aplicará sobre el Window Frame. • Se pueden usar aggregate functions convertidas a window functions o funciones que son exclusivamente window functions. Aggregate Functions AVG() COUNT() MAX() MIN() STD() VARIANCE() SUM() Window Functions • • • • • • • • • • • CUME_DIST() DENSE_RANK() FIRST_VALUE() LAG() LAST_VALUE() LEAD() NTH_VALUE() NTILE() PERCENT_RANK() RANK() ROW_NUMBER() Estructura Partition by Query para construir la tabla select transaccion_id , estatus , date_format(fecha_transaccion_utc, '%Y-%m-%d') fecha , count(transaccion_id) over ( partition by estatus order by day(fecha_transaccion_utc) rows between 2 preceding and 1 following ) as with_rows from banmaya.transacciones where cuenta_id = 200003 and fecha_transaccion_utc < '2021-05-10'; Partition by • Divide la tabla en grupos o particiones sobre los cuales se aplicará la función. • Puede construirse con una o más columnas. • Es opcional dentro de la cláusula OVER. Cuando se omite se toma a todo el conjunto de datos como una sola partición. Estructura Order by Query para construir la tabla select transaccion_id , estatus , date_format(fecha_transaccion_utc, '%Y-%m-%d') fecha , count(transaccion_id) over ( partition by estatus order by day(fecha_transaccion_utc) rows between 2 preceding and 1 following ) as with_rows from banmaya.transacciones where cuenta_id = 200003 and fecha_transaccion_utc < '2021-05-10'; Order by • Ordena los registros dentro de cada partición. • Puede construirse con una o más columnas. • Para algunas funciones es obligatorio incluirla. Estructura Window Frame Window Frame • Un Window Frame es un conjunto de filas relacionadas de alguna forma con cada registro. Son calculadas por separado dentro de cada partición. Query para construir la tabla select transaccion_id , estatus , date_format(fecha_transaccion_utc, '%Y-%m-%d') fecha , count(transaccion_id) over ( partition by estatus order by day(fecha_transaccion_utc) rows between 2 preceding and 1 following ) as with_rows from banmaya.transacciones where cuenta_id = 200003 and fecha_transaccion_utc < '2021-05-10'; Current row Current row • Es opcional dentro del OVER. Los valores por default dependen de si se incluye o no el ORDER BY. • No todas la funciones los admiten. Un window frame tiene la siguiente estructura: ROWS | RANGE BETWEEN límite_inferior AND límite_superior ROWS se usa para acotar por el número de filas. RANGE se usa para acotar por rangos de valores. Los límites se construyen con las siguientes palabras clave (subrayadas en rojo): ¿Sabías que el orden en que se ejecuta el query no es el mismo con el que se escribe? Orden en que se escribe 1. 2. 3. 4. 5. 6. SELECT, AGGREGATE FUNCTIONS, WINDOW FUNCTIONS FROM, JOIN WHERE GROUP BY HAVING ORDER BY Orden en que se ejecuta 1. 2. 3. 4. 5. 6. 7. 8. FROM, JOIN WHERE GROUP BY AGGREGATE FUNCTIONS HAVING WINDOW FUNCTIONS SELECT ORDER BY 27 Aplicaciones más comunes • Se deja como ejercicio construir el query para replicar esta tabla. Usar el filtro cliente_id < 100010. 28 Apéndices Window Functions Window Frame con ROWS vs RANGE Usamos RANGE cuando queremos definir ventanas de tamaño variable que dependan de los valores de la columna de ordenación. Usamos ROWS cuando queremos definir ventanas de tamaño fijo que no dependan de los valores de la columna de ordenación. count(transaccion_id) count(transaccion_id) over ( partition by estatus order by day(fecha_transaccion_utc) range between 2 preceding and 1 following ) as with_range over ( partition by estatus order by day(fecha_transaccion_utc) rows between 2 preceding and 1 following ) as with_rows Día 7 Current row Cuenta entre 2 filas previas hasta la siguiente Current row • • Link para más información. Para replicar las tablas, tomar la estructura del query en esta diapositiva y cambiar las columnas correspondientes. Cuenta donde hayan días entre 5 y 8 Día 8 Cuenta donde hayan días entre 6 y 9 Window Functions Window Frame con RANGE y time frames Anteriormente vimos que la cláusula RANGE dentro de un Window Frame se usa para hacer cálculos que dependan de los valores en la columna de ordenación. Cuando la columna de ordenación es de tipo DATETIME, DATE o TIME podemos definir las ventas por INTERVALOR DE TIEMPO. count(transaccion_id) over ( partition by estatus order by day(fecha_transaccion_utc) RANGE between INTERVAL 2 HOUR preceding and INTERVAL 1 HOUR following ) as with_range_by_hour , count(transaccion_id) over ( partition by estatus order by day(fecha_transaccion_utc) RANGE between INTERVAL 2 DAY preceding and INTERVAL 1 DAY following ) as with_range_by_day • • Se puede usar cualquier intervalo de tiempo enlistado aquí. Para replicar la tabla, tomar la estructura del query en esta diapositiva y agregar las columnas correspondientes. Referencias 1. Documentación Window Functions 2. Cheat Sheet de window functions (ALTAMENTE RECOMENDABLE). Ignorar la cláusula GROUP en el Window Frame. No está disponible en MySQL. U2 – Tema 3 Subqueries, Common Table Expressions (CTEs) & UNIONS Introducción Se nos solicita construir un reporte con todas las transacciones mensuales de los clientes con ID < 100005 en TODO el año 2020. Lo primero que se nos debe ocurrir es hacer lo siguiente: select date_format(t.fecha_transaccion_utc, "%y/%m") MES , count(t.transaccion_id) TXN from clientes cl inner join cuentas cu using(cliente_id) inner join transacciones t using(cuenta_id) where cliente_id < 100005 and year(fecha_transaccion_utc) = group by 1 order by 1; 2020 ¿Cómo puedo agregar los meses de 2020 donde NO hubieron transacciones? Introducción Solución: 1. Repasemos de forma general el problema: Queremos construir lo siguiente: Pero sólo tenemos esto: Identifica todos los elementos/pasos necesarios para construir la tabla final. (Usualmente esto es lo más complicado) 2. “Generar” la información faltante: Crear las tablas (queries) con las que se pueda construir la tabla final. 3. “Unir” todas las tablas creadas con la información existente. • • • 4. Subqueries Common Table Expressions (CTEs) Set Operations (UNION, INTERSECT, etc) Obtener la tabla final 1. Identifica todos los elementos/pasos necesarios para construir la tabla final En este punto debes pensar en todo lo que necesitas para generar la tabla final. Debes poder “imaginar” paso a paso la solución. Paso 1 Tabla MESES Tabla TXNS Paso 2 Unimos las tablas select * from MESES m left join TXNS t using(MES); Paso 3 Construimos la tabla final select m.MES , ifnull(t.TXN, 0) TXN from MESES m left join TXNS t using(MES); NOTA: Esta sólo es una solución de muestra, no es la final. 2. Generar la información faltante Construye un query para generar cada tabla del Paso 1 por separado. Paso 1 Tabla MESES Tabla TXNS Tabla TXNS select date_format(t.fecha_transaccion_utc, "%y/%m") MES , count(t.transaccion_id) TXN from clientes cl inner join cuentas cu using(cliente_id) inner join transacciones t using(cuenta_id) where cliente_id < 100005 and year(fecha_transaccion_utc) = 2020 group by 1 order by 1; Tabla MESES select distinct date_format(t.fecha_transaccion_utc, "%y/%m") MES from transacciones t where year(fecha_transaccion_utc) = 2020 order by 1; 3. “Unir” todas las tablas creadas con la información existente. En este paso podemos hacer uso de cualquiera de las siguientes técnicas: Subquery: Consiste en anidar uno o más queries dentro de otro. select * from ( select distinct date_format(t.fecha_transaccion_utc, "%y/%m") MES from transacciones t where year(fecha_transaccion_utc) = 2020 order by 1 ) MESES left join ( select date_format(t.fecha_transaccion_utc, "%y/%m") MES , count(t.transaccion_id) TXN from clientes cl inner join cuentas cu using(cliente_id) inner join transacciones t using(cuenta_id) where cliente_id < 100005 and year(fecha_transaccion_utc) = 2020 group by 1 ) TXNS using(MES); Resultado Tabla MESES Tabla TXNS NOTA: NO SE RECOMIENDA ANIDAR QUERIES DENTRO DE OTROS QUE YA ESTÉN ANIDADOS. 3. “Unir” todas las tablas creadas con la información existente. En este paso podemos hacer uso de cualquiera de las siguientes técnicas: Common Table Expressions (CTEs): Consiste en generar secuencialmente una o más tablas temporales, que pueden ser usadas repetidas veces dentro de los queries siguientes. with MESES as ( select distinct date_format(t.fecha_transaccion_utc, "%y/%m") MES from transacciones t where year(fecha_transaccion_utc) = 2020 order by 1 ) , TXNS as ( select date_format(t.fecha_transaccion_utc, "%y/%m") MES , count(t.transaccion_id) TXN from clientes cl inner join cuentas cu using(cliente_id) inner join transacciones t using(cuenta_id) where cliente_id < 100005 and year(fecha_transaccion_utc) = 2020 group by 1 ) select * from MESES m left join TXNS t using(MES); Resultado Tabla MESES Tabla TXNS 3. “Unir” todas las tablas creadas con la información existente. En este paso podemos hacer uso de cualquiera de las siguientes técnicas: UNION: “Une” los registros de dos tablas. Se pueden concatenar para unir varias tablas. select * from ( select distinct date_format(t.fecha_transaccion_utc, "%y/%m") MES , 0 TXN -- Columna nueva from transacciones t Tabla MESES where year(fecha_transaccion_utc) = 2020 order by 1 ) MESES UNION ALL select * from ( select date_format(t.fecha_transaccion_utc, "%y/%m") MES , count(t.transaccion_id) TXN from clientes cl Tabla TXNS inner join cuentas cu using(cliente_id) inner join transacciones t using(cuenta_id) where cliente_id < 100005 and year(fecha_transaccion_utc) = 2020 group by 1 ) TXNS; Resultado 4. Obtener la tabla final. Tras haber seleccionado la técnica que mejor se acomode al problema, sólo falta modificar el query para obtener el resultado esperado. En este caso seleccionamos a las CTE: with MESES as ( select distinct date_format(t.fecha_transaccion_utc, "%y/%m") MES from transacciones t where year(fecha_transaccion_utc) = 2020 order by 1 ) , TXNS as ( select date_format(t.fecha_transaccion_utc, "%y/%m") MES , count(t.transaccion_id) TXN from clientes cl inner join cuentas cu using(cliente_id) inner join transacciones t using(cuenta_id) where cliente_id < 100005 and year(fecha_transaccion_utc) = 2020 group by 1 ) select m.MES, ifnull(t.txn, 0) TXN -- Modificación from MESES m left join TXNS t using(MES); Resultado NOTA: Se deja al interesado modificar las tablas con los Subqueries y UNION para obtener el mismo resultado. Conclusiones y recomendaciones Usa cualquiera de las técnicas anteriores cuando necesites construir tablas a partir de los resultados de uno o más queries, siguiendo las siguientes recomendaciones: Técnica Subqueries CTEs* Set operations: UNION Se usa para: • • Anidar dos o más queries SIMPLES. Los queries anidados sólo se usan UNA vez. • • • Queries más complejos. Reutilizar los resultados de un mismo query múltiples veces. Obtener queries más fáciles de leer. • Unir los registros de dos o más queries. Evitar: • • Anidar subqueries dentro de subqueries. Usar queries muy complejos como subqueries. • Crear demasiadas CTEs, ya que complejizan la lectura. • Evitar esta técnica salvo para construcciones muy específicas. Conclusiones y recomendaciones En general: • Las CTEs es la técnica que más recomiendo en la práctica. o Generan queries más fáciles de leer y en ocasiones más rápidos. o Cuando se tienen grandes cantidades de datos, funcionan mejor que las Window Functions (AVANZADO). • Puedes combinar cualquiera de estas técnicas, según lo necesites. Es decir, Subqueries dentro de CTEs, CTEs que almacenen Subqueries, UNION de Subqueries con CTEs y todas las combinaciones que puedas imaginar. • NO ABUSAR DE NINGUNA DE LAS TÉCNICAS. Recuerda que cada subquery o CTE se ejecuta por separado, lo cual puede ser muy costoso computacionalmente. Usa sólo lo necesario. Apéndices CTEs Recursivos Esta técnica nos permite generar secuencias de números, letras, fechas, etc, desde una CTE. Es útil cuando dichas secuencias no se pueden generar con queries de la base de datos, o cuando es muy complicado/tardado obtenerlas. Aquí tenemos dos ejemplos: with RECURSIVE cte(n) as ( select 1 union all select n + 1 from cte where n < 5 ) select * from cte; with RECURSIVE MESES(n, MES) as ( select 1, '20/01’ union all select n + 1 , date_format(date_add(date('2020-01-01'), INTERVAL n MONTH) , "%y/%m") from MESES where n < 12 ) select MES from MESES; Tabla MESES del ejemplo inicial. • Link para más información. Referencias 1. Subqueries (útil para aprender otras formas de usar los subqueries). 2. Documentación Common Table Expressions 3. Set Operations con SQL (lectura ligera y recomendable)