El siguiente conjunto de consultas SQL fue desarrollado con el objetivo de apoyar la toma de decisiones en una empresa del sector automotriz, específicamente en el análisis de anuncios de autos usados.
Mediante estas consultas, es posible:
- Identificar tendencias de mercado (precios, tipos de combustible, años de registro).
- Evaluar el comportamiento de los anuncios (duración activa, frecuencia, condición del vehículo).
- Detectar modelos y marcas con mayor rotación o popularidad.
- Medir el impacto de campañas A/B (en caso de tener campañas activas de marketing).
- Obtener información útil para optimizar estrategias de venta, compra o publicidad.
Estas consultas permiten transformar una base de datos cruda en información accionable, útil para áreas de marketing, ventas, compras o inteligencia comercial.

Estructura de las tablas de la base AutosBD
Precio promedio por marca y modelo
1. Precio promedio por modelo y marca
Calculo el precio promedio (AVG) por cada modelo y marca de los anuncios de autos. Utilizo una Common Table Expression (CTE) llamada promedio_por_modelo para agrupar y obtener estos promedios, y luego realizo LEFT JOINs para obtener los nombres de modelo y marca, mostrando finalmente los 10 modelos con el promedio más alto.
-- Precio promedio por marca y modelo
WITH
promedio_por_modelo AS (
SELECT ROUND(AVG(AN.precio_eur), 2) AS Precio_promedio,
AU.model_id,
AU.marca_id
FROM anuncios AN
LEFT JOIN autos AU
ON AN.car_id = AU.Id
GROUP BY AU.model_id, AU.marca_id
ORDER BY AVG(AN.precio_eur) DESC
)
SELECT PPM.Precio_promedio,
MO.model,
MA.nombre_marca AS marca
FROM promedio_por_modelo PPM
LEFT JOIN models MO
ON PPM.model_id = MO.model_id
LEFT JOIN marcas MA
ON PPM.marca_id = MA.marca_id
LIMIT 10;

2. Precio promedio por marca
Determino el precio promedio (AVG) de los anuncios de autos por cada marca. Para ello, empleo una CTE (promedio_por_marca) que me permite realizar la agregación inicial, y luego un LEFT JOIN para asociar el ID de la marca con su nombre. El resultado final son las 10 marcas con el promedio de precio más elevado.
-- Precio promedio por marca
WITH
promedio_por_marca AS (
SELECT ROUND(AVG(AN.precio_eur), 2) AS Precio_promedio,
AU.marca_id
FROM anuncios AN
LEFT JOIN autos AU
ON AN.car_id = AU.Id
GROUP BY AU.marca_id
ORDER BY AVG(AN.precio_eur) DESC
)
SELECT PPM.Precio_promedio,
MA.nombre_marca AS marca
FROM promedio_por_marca PPM
LEFT JOIN marcas MA
ON PPM.marca_id = MA.marca_id
LIMIT 10;

Distribución de tipos de combustible
3. % de Autos por Tipo de Combustible
Calculo el porcentaje de vehículos según su tipo de combustible. Primero, obtengo el conteo total de anuncios usando una CTE (total_anuncios). Posteriormente, realizo un RIGHT JOIN y un CROSS JOIN para vincular los autos con los anuncios y la tabla de totales, lo que me permite calcular la proporción de cada tipo de combustible.
-- % de autos diésel, gasolina, eléctricos, etc.
WITH total_anuncios AS (
SELECT COUNT(*) AS total FROM anuncios
)
SELECT
AU.fuel_type,
COUNT(AN.car_id) AS cantidad,
ROUND(COUNT(AN.car_id) * 100.0 / TA.total, 3) AS porcentaje
FROM autos AU
RIGHT JOIN anuncios AN ON AU.Id = AN.car_id
CROSS JOIN total_anuncios TA
GROUP BY AU.fuel_type, TA.total;

Top 5 autos más potentes por marca o modelo
4. Top 5 autos más potentes por Marca/Modelo
Identifico los 5 autos más potentes (potencia máxima en CV) que existen por modelo y marca. Utilizo una CTE (ranking) para agrupar y obtener esa potencia máxima, y luego aplico la función de ventana ROW_NUMBER() para asignarles un ranking en la salida final.
-- Top 5 autos más potentes por marca o modelo
WITH ranking AS (
SELECT
MO.model,
MA.nombre_marca AS marca,
MAX(A.potencia_CV) AS potencia_maxima
FROM autos A
JOIN models MO ON A.model_id = MO.model_id
JOIN marcas MA ON A.marca_id = MA.marca_id
GROUP BY MO.model, MA.nombre_marca
ORDER BY potencia_maxima DESC
LIMIT 5
)
SELECT
ROW_NUMBER() OVER (ORDER BY potencia_maxima DESC) AS Top,
model,
marca,
potencia_maxima
FROM
ranking;

Duración del anuncio (fechas)
5. Duración promedio del anuncio
Calcula la duración promedio en días de los anuncios, utilizando la función DATEDIFF() entre ultima_vez_visto y fecha_anuncio, con el resultado redondeado a dos decimales.
-- Duración promedio del anuncio
SELECT
ROUND(AVG(DATEDIFF(ultima_vez_visto, fecha_anuncio)),2) AS días_promedio_deL_anuncio
FROM anuncios;

6. Duración máxima del anuncio
Obtiene la duración máxima en días registrada para un anuncio, calculada mediante la función DATEDIFF() entre ultima_vez_visto y fecha_anuncio.
-- Duración máxima del anuncio
SELECT
MAX(DATEDIFF(ultima_vez_visto, fecha_anuncio)) AS días_maximos_deL_anuncio
FROM anuncios;

Tasa de anuncios con daños no reparados
7. Tasa de anuncios con daños no reparados
Determina el porcentaje de anuncios con daños no reparados ('ja'). Emplea una agregación condicional (SUM(CASE WHEN ... THEN 1 ELSE 0 END)) dividida por el COUNT(*) total para obtener la proporción.
-- Tasa de anuncios con daños no reparados
SELECT
SUM(CASE
WHEN daño_no_reparado = 'ja' THEN 1
ELSE 0 END) * 100.0 / COUNT(*) AS porcentaje_dañados
FROM anuncios;

Participación en pruebas A/B
8. Porcentaje de anuncios con prueba A/B "test" vs "control"
Calcula el porcentaje de anuncios asignados a la categoría "test" respecto al total de anuncios que pertenecen a las categorías "test" o "control", utilizando agregación condicional (SUM(CASE WHEN ...)) para el numerador y el denominador.
-- % de anuncios con prueba A/B "test" vs "control" 'unknown'
SELECT SUM(CASE
WHEN prueba_ab = "test"
THEN 1 ELSE 0 END)/
SUM(CASE
WHEN prueba_ab = "test" OR prueba_ab = "control"
THEN 1 ELSE 0 END)
AS "% anuncios test"
FROM anuncios;

Cantidad de anuncios por año de registro
9. Cantidad de anuncios por año de registro
Esta consulta cuantifica el número total de anuncios registrados por año. Utiliza la función YEAR() para extraer el año de la fecha_anuncio y agrupa los resultados para obtener el conteo de anuncios por cada año fiscal.
-- Cantidad de anuncios por año de registro
SELECT
YEAR(fecha_anuncio) AS Año,
COUNT(YEAR(fecha_anuncio)) AS cantidad_anuncios
FROM anuncios
GROUP BY YEAR(fecha_anuncio);

Cantidad de anuncio por mes
10. Tendencia de nuevos anuncios por mes
Esta consulta analiza la tendencia de nuevos anuncios por mes. Emplea DATE_FORMAT() para extraer el nombre del mes de fecha_anuncio, agrupa los resultados por mes y luego ordena el conteo de anuncios de forma descendente para identificar los meses con mayor volumen de publicación.
-- Tendencia de nuevos anuncios por mes
SELECT
DATE_FORMAT(fecha_anuncio, '%M') AS mes,
COUNT(MONTH(fecha_anuncio)) AS cantidad_anuncios
FROM anuncios
GROUP BY mes
ORDER BY cantidad_anuncios DESC;

Top 5 mayores precios promedios por región
11. Precio promedio mayor por región
Esta consulta identifica las 5 regiones con el precio promedio (AVG) de anuncio más alto (precio_eur). Utiliza una función de ventana ROW_NUMBER() para rankear los resultados por codigo_postal, tras agrupar y ordenar por el precio promedio de forma descendente.
-- Precio promedio mayor por región
SELECT
ROW_NUMBER () OVER (ORDER BY AVG(precio_eur) DESC) AS Top,
codigo_postal,
AVG(precio_eur) AS precio_promedio
FROM anuncios
GROUP BY codigo_postal
ORDER BY precio_promedio DESC
LIMIT 5;

- Se definieron claves foráneas para establecer relaciones entre tablas y garantizar la integridad referencial.
- Se crearon índices para acelerar búsquedas, filtros y joins.
Estas acciones mejoran el rendimiento de las consultas y aseguran una estructura de datos coherente y optimizada.
Creación de índices
-- Se crean los índices para las tablas
CREATE INDEX idx_autos_id ON autos(Id);
CREATE INDEX idx_marca_id ON marcas(marca_id);
CREATE INDEX idx_models_id ON models(model_id);
CREATE INDEX idx_vehicle_type ON vehicle_type(type_id);
Columnas tipo entero (INT)
-- Precio vs kilometraje por modelo/año.
WITH max_min AS
(SELECT
MAX(kilometraje) AS ma,
MIN(kilometraje) AS mi
FROM
anuncios),
rango_km AS
(SELECT CASE
WHEN A.kilometraje
BETWEEN MM.mi AND MM.mi + (MM.ma/5)*1
THEN CONCAT(MM.mi, ' - ', ROUND(MM.mi + (MM.ma/5)*1, 0))
WHEN A.kilometraje
BETWEEN MM.mi + (MM.ma/5)*1 AND MM.mi + (MM.ma/5)*2
THEN CONCAT(ROUND(MM.mi + (MM.ma/5)*1, 0), ' - ', ROUND(MM.mi + (MM.ma/5)*2, 0))
WHEN A.kilometraje
BETWEEN MM.mi + (MM.ma/5)*2 AND MM.mi + (MM.ma/5)*3
THEN CONCAT(ROUND(MM.mi + (MM.ma/5)*2, 0), ' - ', ROUND(MM.mi + (MM.ma/5)*3, 0))
WHEN A.kilometraje
BETWEEN MM.mi + (MM.ma/5)*3 AND MM.mi + (MM.ma/5)*4
THEN CONCAT(ROUND(MM.mi + (MM.ma/5)*3, 0), ' - ', ROUND(MM.mi + (MM.ma/5)*4, 0))
ELSE
CONCAT(ROUND(MM.mi + (MM.ma/5)*4, 0), ' - ', ROUND(MM.mi + (MM.ma/5)*5, 0))
END AS rango_kilometraje,
precio_eur
FROM anuncios A
CROSS JOIN max_min MM
)
SELECT
rango_kilometraje,
AVG(precio_eur) AS precio_promedio
FROM
rango_km
GROUP BY rango_kilometraje
ORDER BY precio_promedio DESC;
