Volver a la lista de artículos Artículos
Lectura de 10 minutos

Preguntas de práctica de SQL de nivel intermedio

¿Está preparado para llevar sus conocimientos de SQL al siguiente nivel? Estas preguntas de nivel intermedio le ayudarán a practicar situaciones reales con GROUP BY, CASE WHEN, subconsultas, CTE y mucho más.

Si ya domina los conceptos básicos de SQL y se siente cómodo escribiendo consultas sencillas, ha llegado el momento de subir de nivel. Las habilidades SQL intermedias son clave para manejar informes del mundo real, análisis más profundos y resolver tareas de datos más complejas que van más allá de las sentencias básicas SELECT.

En este artículo, practicará técnicas esenciales como GROUP BY, CASE WHEN, subconsultas y expresiones comunes de tabla (CTE). Estos ejercicios se basan en escenarios realistas, por lo que son ideales para aspirantes a analistas de datos, desarrolladores o cualquier persona que se esté preparando para entrevistas técnicas. Intente resolver cada desafío antes de comprobar la solución para perfeccionar sus habilidades de resolución de problemas.

Si quieres profundizar en estos conceptos de forma más estructurada, consulta el curso Cómo crear informes básicos con SQL en LearnSQL.es. Se trata de un curso práctico para principiantes centrado en la conversión de datos brutos en informes significativos, un paso perfecto después de completar este conjunto de preguntas de práctica.

Base de datos para los ejercicios

Para resolver los ejercicios de este artículo, utilizará tres tablas: cliente, producto y compra.

  • La tabla de clientes incluye: customer_id, first_name, last_name, email, signup_date, city, y country.
  • La tabla de productos contiene: product_id, name, category, price, y launch_date.
  • La tabla purchase registra las transacciones e incluye: purchase_id, customer_id, product_id, quantity, total_amount, y purchase_date.

Ejercicio 1: Resumen mensual de ingresos

Ejercicio:
Desglosar los datos de compra por año y mes, y mostrar el número de pedidos (orders) y los ingresos totales (revenue) para cada período.

Solución:

SELECT
	EXTRACT(YEAR FROM purchase_date) AS purchase_year,
	EXTRACT(MONTH FROM purchase_date) AS purchase_month,
	COUNT(*) AS orders,
	SUM(total_amount) AS revenue
FROM purchase
GROUP BY
	EXTRACT(YEAR FROM purchase_date),
	EXTRACT(MONTH FROM purchase_date)
ORDER BY
	purchase_year,
	purchase_month;

Explicación:

Para resolver este problema, necesitamos agrupar las compras tanto por año como por mes, de forma que cada periodo (por ejemplo, enero de 2023, febrero de 2023) se trate por separado. Empezaremos extrayendo el año y el mes de purchase_date mediante la función EXTRACT, que forma parte del estándar SQL y está ampliamente soportada. Algunas bases de datos pueden tener sus propias alternativas, como DATEPART o TO_CHAR. Agrupar por purchase_year y purchase_month evita que todas las compras de enero de diferentes años se combinen en un solo grupo. A continuación, contamos el número de pedidos y sumamos los ingresos totales de cada periodo.

Ejercicio 2: Buscar pedidos recientes

Ejercicio:

Encuentre todas las compras realizadas en los últimos 30 días. Mostrar todas las columnas de la tabla de compras.

Solución:

SELECT *
FROM purchase
WHERE purchase_date >= CURRENT_DATE - INTERVAL 30 DAY;

Explicación:

Para encontrar las compras de los últimos 30 días, filtramos la tabla purchase utilizando una condición de fecha. Comparamos purchase_date con la fecha actual menos un intervalo de 30 días. La palabra clave CURRENT_DATE indica la fecha actual y INTERVAL 30 DAY es una forma estándar de restar días en ANSI SQL. Esto garantiza que sólo se devuelvan las filas en las que la compra se haya producido en los últimos 30 días. Algunas bases de datos pueden utilizar una sintaxis ligeramente diferente para los intervalos de fechas, pero la lógica sigue siendo la misma.

Ejercicio 3: Productos con precios superiores a la media de su categoría

Ejercicio:

Devuelva todos los productos que son más caros que el precio medio de su categoría. Mostrar todas las columnas de la tabla de compras.

Solución:

SELECT *
FROM product p
WHERE price >
  	(SELECT AVG(price)
   	FROM product
   	WHERE category = p.category);

Explicación:

Para resolverlo, comparamos el precio de cada producto con el precio medio de los productos de su misma categoría. Utilizamos una subconsulta que calcula la media price para un determinado category, y luego comprueba si el precio del producto actual es superior a esa media. Se trata de una subconsulta correlacionada: se ejecuta una vez por cada fila de la consulta externa, utilizando p.category para emparejar las categorías. Utilizamos SELECT * para devolver todas las columnas de la tabla de productos para los productos que cumplen la condición.

Ejercicio 4: Segmentos de precios: Económico, Medio, Premium

Ejercicio:

Asigne a cada producto un segmento de precio: "económico" para menos de 20 $, "medio" para entre 20 $ y 99,99 $ y "premium" para 100 $ o más.

Solución:

SELECT
	product_id,
	name,
	price,
	CASE
    		WHEN price < 20 THEN 'budget'
    		WHEN price BETWEEN 20 AND 99.99 THEN 'middle'
    		ELSE 'premium'
	END AS price_segment
FROM product;

Explicación:

En esta consulta, utilizamos la expresión CASE para asignar cada producto a un segmento de precios en función de su price. La sintaxis CASE funciona como una estructura if-else: comprueba las condiciones en orden y devuelve el valor correspondiente. Aquí, los productos de menos de 20 dólares se etiquetan como 'budget', los de entre 20 y 99,99 dólares como 'middle', y los de 100 dólares o más como 'premium'. El resultado incluye los detalles originales del producto junto con una nueva columna price_segment.

Ejercicio 5: Agrupación de clientes por región

Ejercicio:
Agrupe los clientes en regiones en función de su país. Suponga que países como 'USA', 'Canada', y 'Mexico' pertenecen a 'North America', mientras que todos los demás entran en 'Other'. Para cada región, cuente cuántos clientes están asignados a ella. El resultado debe incluir region y customer_count.

Solución:

SELECT
	CASE
    	WHEN country IN ('USA', 'Canada', 'Mexico') THEN 'North America'
    	ELSE 'Other'
	END AS region,
	COUNT(*) AS customer_count
FROM customer
GROUP BY
	CASE
    	WHEN country IN ('USA', 'Canada', 'Mexico') THEN 'North America'
    	ELSE 'Other'
	END;

Explicación:

Esta consulta utiliza la expresión CASE para agrupar los países en regiones personalizadas: 'North America' para 'USA', 'Canada' y 'Mexico', y 'Other' para el resto. CASE se utiliza a menudo para crear categorías propias basadas en los valores de las columnas. Una vez definidas estas regiones personalizadas, podemos agrupar por ellas para contar cuántos clientes entran en cada una. Este es un patrón común cuando se trabaja con datos geográficos, niveles de precios o cualquier lógica de clasificación personalizada.

Ejercicio 6: Gasto del cliente por categoría

Ejercicio:

Para cada cliente, calcule cuánto ha gastado en productos de las categorías "Electrónica", "Ropa" y "Hogar". El resultado debe incluir customer_id, electronics_spend, clothing_spend, y home_spend como nombres de columna.

Solución:

SELECT
	customer_id,
	SUM(CASE 
WHEN category = 'Electronics' 
THEN total_amount ELSE 0 END) AS electronics_spend,
	SUM(CASE 
WHEN category = 'Clothing'	
THEN total_amount ELSE 0 END) AS clothing_spend,
	SUM(CASE 
WHEN category = 'Home'    	
THEN total_amount ELSE 0 END) AS home_spend
FROM purchase  p
JOIN product pr 
ON p.product_id = pr.product_id
GROUP BY customer_id;

Explicación:

En esta consulta, calculamos cuánto gastó cada cliente en productos de categorías específicas utilizando CASE WHEN dentro de funciones agregadas. CASE nos permite comprobar la categoría de cada producto y devolver total_amount sólo si coincide con la que nos interesa; de lo contrario, devuelve 0. Repetimos este patrón para 'Electronics', 'Clothing', y 'Home', y envolvemos cada uno en SUM() para obtener el gasto total por categoría. El resultado incluye una fila por customer_id con columnas separadas para el gasto en cada categoría. Esta técnica es útil para crear resúmenes tipo pivote directamente en SQL.

Ejercicio 7. Gasto medio por cliente Gasto Medio por Cliente

Ejercicio:
Calcule el importe total gastado por cada cliente y, a continuación, devuelva la media de los totales de estos clientes. El resultado final debe incluir una sola columna: avg_customer_spend.

Solución:

WITH customer_spend AS (
	SELECT customer_id, SUM(total_amount) AS spend
	FROM purchase
	GROUP BY customer_id
)
SELECT AVG(spend) AS avg_customer_spend
FROM customer_spend;

Explicación:

Para resolver esto, primero calculamos cuánto ha gastado cada cliente en total agrupando los datos de purchase por customer_id y sumando los de total_amount. Hacemos esto en una Expresión de Tabla Común (CTE) llamada customer_spend. A continuación, en la consulta principal, calculamos la media de estos totales utilizando AVG(spend) y la devolvemos como avg_customer_spend. Este enfoque ayuda a separar los cálculos intermedios y mantiene la consulta limpia y legible.

Ejercicio 8: Clientes por encima de la media

Ejercicio:

Busque el importe total gastado por cada cliente y el gasto medio de todos los clientes. Devuelva sólo aquellos clientes cuyo gasto total sea superior a la media. El resultado debe incluir las columnas customer_id y total_spend.

Solución:

WITH customer_spend AS ( -- total spend per customer
	SELECT
    	customer_id,
    		SUM(total_amount) AS total_spend
	FROM purchase
	GROUP BY customer_id
), avg_spend AS ( -- 2) average of those totals
	SELECT
    	AVG(total_spend) AS avg_total_spend
	FROM customer_spend
)
SELECT
	cs.customer_id,
	cs.total_spend
FROM customer_spend cs, avg_spend a -- join to include the average in each row
WHERE cs.total_spend > a.avg_total_spend
ORDER BY cs.total_spend DESC;

Explicación:

Comenzamos calculando el gasto total por cliente mediante un CTE denominado customer_spend. Luego, en un segundo CTE llamado avg_spend, calculamos el promedio de esos totales. En la consulta principal, comparamos el gasto total de cada cliente con la media uniendo ambos CTE. Se devuelven los clientes cuyo total_spend es mayor que la media, junto con su customer_id. El uso de dos CTE facilita el seguimiento de la lógica y mantiene limpia la consulta.

Ejercicio 9: Ventas de Productos como Porcentaje del Total

Ejercicio:

Calcule los ingresos totales de cada producto. Luego, para cada producto, calcule qué porcentaje contribuye a los ingresos totales. El resultado debe incluir product_id, revenue, y pct_of_total.

Solución:

WITH product_sales AS (
	SELECT product_id, SUM(total_amount) AS revenue
	FROM purchase
	GROUP BY product_id
), total_revenue AS (
	SELECT SUM(revenue) AS total
	FROM product_sales
)
SELECT
	ps.product_id,
	ps.revenue,
	ROUND(100.0 * ps.revenue / tr.total, 2) AS pct_of_total
FROM product_sales ps
CROSS JOIN total_revenue tr
ORDER BY pct_of_total DESC;

Explicación:

Comenzamos calculando los ingresos totales por producto utilizando una Expresión de Tabla Común (CTE) llamada product_sales. A continuación, creamos otra CTE denominada total_revenue para calcular los ingresos totales de todos los productos. En la consulta final, unimos ambas CTE utilizando CROSS JOIN para que cada fila de producto tenga acceso al valor total de ingresos. A continuación, calculamos el porcentaje de contribución de cada producto dividiendo sus ingresos por el total y multiplicando por 100. El resultado incluye , y . El resultado incluye product_id, revenue, y pct_of_total, ordenados de mayor a menor porcentaje.

Ejercicio 10: Activo pero no en diciembre

Ejercicio:

Encuentre los clientes que hicieron más de un pedido pero nunca realizaron una compra en diciembre. El resultado debe incluir customer_id, first_name, y last_name.

Solución:

WITH multi_order AS (
	SELECT customer_id
	FROM purchase
	GROUP BY customer_id
	HAVING COUNT(*) > 1
), december_buyers AS (
	SELECT DISTINCT customer_id
	FROM purchase
	WHERE EXTRACT(MONTH FROM purchase_date) = 12
)
SELECT
	c.customer_id,
	c.first_name,
	c.last_name
FROM customer c
JOIN multi_order mo 
ON c.customer_id = mo.customer_id
LEFT JOIN december_buyers d 
ON c.customer_id = d.customer_id
WHERE d.customer_id IS NULL;

Explicación:

Esta consulta encuentra clientes que realizaron más de un pedido pero ninguno en diciembre. En primer lugar, la ETC multi_order identifica los clientes con más de una compra agrupándolos por customer_id y utilizando HAVING COUNT(*) > 1. La ETC december_buyers selecciona todos los clientes que realizaron al menos una compra en diciembre extrayendo el mes de purchase_date. En la consulta final, unimos la tabla customer con multi_order para obtener los clientes relevantes y, a continuación, utilizamos una UNIÓN IZQUIERDA con december_buyers para comprobar quién no realizó ninguna compra en diciembre. Filtramos los que sí lo hicieron comprobando WHERE d.customer_id IS NULL. El resultado sólo incluye customer_id, first_name y last_name.

Más información en Ejercicio de SQL

Practicar SQL con regularidad es una de las formas más efectivas de pasar de entender la teoría a sentirse seguro en escenarios del mundo real. Los ejercicios de este artículo cubren temas intermedios básicos como GROUP BY, CASE WHEN, subconsultas y CTE, todas ellas habilidades que aparecen con frecuencia en el análisis de datos, la elaboración de informes y las entrevistas técnicas. Si has llegado hasta aquí, estás en el buen camino para convertirte en un experto en SQL.

Pero no se detenga aquí. La constancia es clave a la hora de aprender SQL, y la mejor forma de seguir mejorando es resolviendo más problemas prácticos. Si lo que busca es una práctica estructurada y práctica con feedback instantáneo, le recomendamos encarecidamente el curso Ejercicio de SQL en LearnSQL.es. Está diseñado para ayudarle a fortalecer sus habilidades a través de desafíos de consulta reales que se construyen unos sobre otros - sin palabrería, sólo la práctica que importa.

Si tu objetivo es conseguir un trabajo relacionado con los datos, automatizar informes o trabajar de forma más eficaz con bases de datos, el curso Ejercicio de SQL es el siguiente paso que merece la pena dar.