Práctica SQL: Consultas con base de datos de aerolíneas
Practica SQL con esquema de aerolíneas: vuelos, reservas y pasajeros. Ejercicios con autounión de aeropuertos, subconsultas correlacionadas y GROUP BY.
Use this file to discover all available pages before exploring further.
Esta práctica explora una base de datos de operaciones aéreas que incluye aerolíneas, aeropuertos, aviones, vuelos, pasajeros y reservas. El esquema introduce dos patrones que aparecen con frecuencia en bases de datos reales y que requieren atención especial en SQL: la autounión de la tabla aeropuertos (una misma tabla referenciada dos veces en el mismo FROM para representar origen y destino) y las subconsultas correlacionadas para calcular estadísticas por vuelo o por pasajero. A través de las consultas progresivas aprenderás a navegar estas relaciones con confianza y a escribir análisis de negocio directamente en SQL.
Las columnas origen_id y destino_id en la tabla vuelos son dos claves foráneas distintas que apuntan a la misma tablaaeropuertos. Para usar ambas en un SELECT, debes incluir aeropuertos dos veces en el FROM con alias diferentes (por ejemplo, ao para origen y ad para destino). Olvidar usar alias distintos es el error más común al escribir consultas sobre este esquema.
-- Patrón correcto: aeropuertos referenciado con dos aliasesFROM vuelos vJOIN aeropuertos ao ON v.origen_id = ao.aeropuerto_id -- origenJOIN aeropuertos ad ON v.destino_id = ad.aeropuerto_id -- destino
Comienza por entender qué aerolíneas existen y cuántos aviones opera cada una:
SELECT al.nombre AS aerolinea, al.codigo_iata, al.pais, COUNT(av.avion_id) AS total_aviones, SUM(av.capacidad) AS capacidad_total_pasajerosFROM aerolineas alLEFT JOIN aviones av ON al.aerolinea_id = av.aerolinea_idGROUP BY al.aerolinea_id, al.nombre, al.codigo_iata, al.paisORDER BY total_aviones DESC;
2
Explorar los vuelos con origen y destino
Usa el patrón de doble join con aeropuertos para ver las rutas disponibles:
-- Vuelos disponibles con origen y destino legiblesSELECT v.vuelo_id, al.nombre AS aerolinea, ao.nombre AS origen, ao.ciudad AS ciudad_origen, ad.nombre AS destino, ad.ciudad AS ciudad_destino, v.fecha_salida, v.fecha_llegada, v.precio_baseFROM vuelos vJOIN aerolineas al ON v.aerolinea_id = al.aerolinea_idJOIN aeropuertos ao ON v.origen_id = ao.aeropuerto_idJOIN aeropuertos ad ON v.destino_id = ad.aeropuerto_idORDER BY v.fecha_salida;
3
Explorar reservas y pasajeros
Consulta quién está en cada vuelo y en qué clase viaja:
-- Pasajeros en cada vuelo con su clase y asientoSELECT v.vuelo_id, ao.ciudad AS origen, ad.ciudad AS destino, v.fecha_salida, p.nombre || ' ' || p.apellido AS pasajero, r.clase, r.asiento, r.precio_pagadoFROM reservas rJOIN vuelos v ON r.vuelo_id = v.vuelo_idJOIN pasajeros p ON r.pasajero_id = p.pasajero_idJOIN aeropuertos ao ON v.origen_id = ao.aeropuerto_idJOIN aeropuertos ad ON v.destino_id = ad.aeropuerto_idORDER BY v.fecha_salida, r.clase;
4
Analizar ingresos por aerolínea
Agrega los ingresos totales, número de reservas y precio promedio por aerolínea:
-- Ingresos totales por aerolíneaSELECT al.nombre AS aerolinea, COUNT(r.reserva_id) AS total_reservas, SUM(r.precio_pagado) AS ingresos_totales, AVG(r.precio_pagado) AS precio_promedioFROM aerolineas alJOIN vuelos v ON al.aerolinea_id = v.aerolinea_idJOIN reservas r ON v.vuelo_id = r.vuelo_idGROUP BY al.aerolinea_id, al.nombreORDER BY ingresos_totales DESC;
5
Identificar pasajeros frecuentes
Encuentra los pasajeros que han realizado más de 3 vuelos:
-- Pasajeros frecuentes con más de 3 vuelos reservadosSELECT p.nombre || ' ' || p.apellido AS pasajero, p.nacionalidad, p.email, COUNT(r.reserva_id) AS total_vuelosFROM pasajeros pJOIN reservas r ON p.pasajero_id = r.pasajero_idGROUP BY p.pasajero_id, p.nombre, p.apellido, p.nacionalidad, p.emailHAVING COUNT(r.reserva_id) > 3ORDER BY total_vuelos DESC;
-- Todos los vuelos directos de Bogotá a MiamiSELECT v.vuelo_id, al.nombre AS aerolinea, v.fecha_salida, v.fecha_llegada, av.modelo AS avion, av.capacidad, v.precio_baseFROM vuelos vJOIN aerolineas al ON v.aerolinea_id = al.aerolinea_idJOIN aviones av ON v.avion_id = av.avion_idJOIN aeropuertos ao ON v.origen_id = ao.aeropuerto_idJOIN aeropuertos ad ON v.destino_id = ad.aeropuerto_idWHERE ao.ciudad = 'Bogotá' AND ad.ciudad = 'Miami'ORDER BY v.fecha_salida;
Reservas de un pasajero específico
-- Historial de viajes de un pasajero dado su pasaporteSELECT r.reserva_id, al.nombre AS aerolinea, ao.ciudad || ' → ' || ad.ciudad AS ruta, v.fecha_salida, r.clase, r.asiento, r.precio_pagadoFROM reservas rJOIN vuelos v ON r.vuelo_id = v.vuelo_idJOIN aerolineas al ON v.aerolinea_id = al.aerolinea_idJOIN aeropuertos ao ON v.origen_id = ao.aeropuerto_idJOIN aeropuertos ad ON v.destino_id = ad.aeropuerto_idJOIN pasajeros p ON r.pasajero_id = p.pasajero_idWHERE p.pasaporte = 'AB123456'ORDER BY v.fecha_salida;
-- Top 10 rutas por número de reservasSELECT ao.ciudad || ' → ' || ad.ciudad AS ruta, ao.codigo_iata || '-' || ad.codigo_iata AS codigos, COUNT(r.reserva_id) AS total_reservas, SUM(r.precio_pagado) AS ingresos_ruta, ROUND(AVG(r.precio_pagado), 2) AS precio_promedioFROM reservas rJOIN vuelos v ON r.vuelo_id = v.vuelo_idJOIN aeropuertos ao ON v.origen_id = ao.aeropuerto_idJOIN aeropuertos ad ON v.destino_id = ad.aeropuerto_idGROUP BY ao.ciudad, ao.codigo_iata, ad.ciudad, ad.codigo_iataORDER BY total_reservas DESCLIMIT 10;
Ocupación promedio por vuelo
-- Porcentaje de ocupación de cada vuelo (reservas / capacidad del avión)SELECT v.vuelo_id, al.nombre AS aerolinea, ao.ciudad || ' → ' || ad.ciudad AS ruta, v.fecha_salida, av.capacidad, COUNT(r.reserva_id) AS pasajeros_reservados, ROUND( COUNT(r.reserva_id) * 100.0 / av.capacidad, 1 ) AS pct_ocupacionFROM vuelos vJOIN aerolineas al ON v.aerolinea_id = al.aerolinea_idJOIN aviones av ON v.avion_id = av.avion_idJOIN aeropuertos ao ON v.origen_id = ao.aeropuerto_idJOIN aeropuertos ad ON v.destino_id = ad.aeropuerto_idLEFT JOIN reservas r ON v.vuelo_id = r.vuelo_idGROUP BY v.vuelo_id, al.nombre, ao.ciudad, ad.ciudad, v.fecha_salida, av.capacidadORDER BY pct_ocupacion DESC;
Distribución de clases por aerolínea
-- Cuántas reservas por clase tiene cada aerolíneaSELECT al.nombre AS aerolinea, r.clase, COUNT(*) AS reservas, SUM(r.precio_pagado) AS ingresos_por_claseFROM aerolineas alJOIN vuelos v ON al.aerolinea_id = v.aerolinea_idJOIN reservas r ON v.vuelo_id = r.vuelo_idGROUP BY al.aerolinea_id, al.nombre, r.claseORDER BY al.nombre, r.clase;
Vuelos con precio mayor al promedio de su aerolínea
-- Vuelos cuyo precio_base supera el promedio de la aerolínea que los operaSELECT v.vuelo_id, al.nombre AS aerolinea, ao.ciudad || ' → ' || ad.ciudad AS ruta, v.precio_base, ROUND( (SELECT AVG(v2.precio_base) FROM vuelos v2 WHERE v2.aerolinea_id = v.aerolinea_id), 2 ) AS promedio_aerolineaFROM vuelos vJOIN aerolineas al ON v.aerolinea_id = al.aerolinea_idJOIN aeropuertos ao ON v.origen_id = ao.aeropuerto_idJOIN aeropuertos ad ON v.destino_id = ad.aeropuerto_idWHERE v.precio_base > ( SELECT AVG(v2.precio_base) FROM vuelos v2 WHERE v2.aerolinea_id = v.aerolinea_id)ORDER BY al.nombre, v.precio_base DESC;
Pasajeros sin vuelos en el último semestre
-- Pasajeros que no han viajado en los últimos 6 mesesSELECT p.pasajero_id, p.nombre || ' ' || p.apellido AS pasajero, p.email, p.nacionalidad, MAX(v.fecha_salida) AS ultimo_vueloFROM pasajeros pJOIN reservas r ON p.pasajero_id = r.pasajero_idJOIN vuelos v ON r.vuelo_id = v.vuelo_idGROUP BY p.pasajero_id, p.nombre, p.apellido, p.email, p.nacionalidadHAVING MAX(v.fecha_salida) < CURRENT_DATE - INTERVAL '6 months'ORDER BY ultimo_vuelo ASC;
Aeropuertos con mayor tráfico (origen + destino combinado)
-- Movimiento total de cada aeropuerto (vuelos salientes + entrantes)SELECT ap.nombre AS aeropuerto, ap.ciudad, ap.codigo_iata, SUM(mov.cantidad) AS movimientos_totalesFROM aeropuertos apJOIN ( -- Vuelos salientes SELECT origen_id AS aeropuerto_id, COUNT(*) AS cantidad FROM vuelos GROUP BY origen_id UNION ALL -- Vuelos entrantes SELECT destino_id AS aeropuerto_id, COUNT(*) AS cantidad FROM vuelos GROUP BY destino_id) mov ON ap.aeropuerto_id = mov.aeropuerto_idGROUP BY ap.aeropuerto_id, ap.nombre, ap.ciudad, ap.codigo_iataORDER BY movimientos_totales DESC;
Encuentra el vuelo más antiguo (por fecha_salida) de cada pasajero. Muestra el nombre del pasajero, la ruta de su primer vuelo, la fecha y el precio pagado.Solución:
-- Usando subconsulta con MIN por pasajeroSELECT p.nombre || ' ' || p.apellido AS pasajero, ao.ciudad || ' → ' || ad.ciudad AS primer_ruta, v.fecha_salida AS fecha_primer_vuelo, r.precio_pagadoFROM reservas rJOIN pasajeros p ON r.pasajero_id = p.pasajero_idJOIN vuelos v ON r.vuelo_id = v.vuelo_idJOIN aeropuertos ao ON v.origen_id = ao.aeropuerto_idJOIN aeropuertos ad ON v.destino_id = ad.aeropuerto_idWHERE v.fecha_salida = ( SELECT MIN(v2.fecha_salida) FROM reservas r2 JOIN vuelos v2 ON r2.vuelo_id = v2.vuelo_id WHERE r2.pasajero_id = r.pasajero_id)ORDER BY fecha_primer_vuelo;
Ejercicio 2: Aerolíneas que operan en todos los países
Lista las aerolíneas que tienen vuelos que aterrizan (destino) en al menos 3 países diferentes. Muestra el nombre de la aerolínea, su país de origen y el número de países destino distintos.Solución:
SELECT al.nombre AS aerolinea, al.pais AS pais_origen, COUNT(DISTINCT ad.pais) AS paises_destinoFROM aerolineas alJOIN vuelos v ON al.aerolinea_id = v.aerolinea_idJOIN aeropuertos ad ON v.destino_id = ad.aeropuerto_idGROUP BY al.aerolinea_id, al.nombre, al.paisHAVING COUNT(DISTINCT ad.pais) >= 3ORDER BY paises_destino DESC;
Ejercicio 3: Diferencia entre precio base y precio pagado
Calcula, para cada vuelo que tiene al menos una reserva, la diferencia promedio entre el precio_base del vuelo y el precio_pagado en las reservas. Ordena de mayor a menor diferencia promedio. Esto puede indicar vuelos donde se ofrecieron descuentos o recargos por clase.Solución:
SELECT v.vuelo_id, al.nombre AS aerolinea, ao.ciudad || ' → ' || ad.ciudad AS ruta, v.fecha_salida, v.precio_base, ROUND(AVG(r.precio_pagado), 2) AS precio_promedio_pagado, ROUND(AVG(r.precio_pagado) - v.precio_base, 2) AS diferencia_promedioFROM vuelos vJOIN aerolineas al ON v.aerolinea_id = al.aerolinea_idJOIN aeropuertos ao ON v.origen_id = ao.aeropuerto_idJOIN aeropuertos ad ON v.destino_id = ad.aeropuerto_idJOIN reservas r ON v.vuelo_id = r.vuelo_idGROUP BY v.vuelo_id, al.nombre, ao.ciudad, ad.ciudad, v.fecha_salida, v.precio_baseORDER BY diferencia_promedio DESC;
Ejercicio 4: Pasajeros que volaron en todas las clases disponibles
Encuentra los pasajeros que han viajado en todas las clases disponibles en la tabla reservas (es decir, si existen tres clases distintas, el pasajero debe haber tenido una reserva en cada una de ellas).Solución:
-- Contar clases distintas en toda la tabla-- y comparar con cuántas clases distintas tiene cada pasajeroSELECT p.nombre || ' ' || p.apellido AS pasajero, p.nacionalidad, COUNT(DISTINCT r.clase) AS clases_viajadasFROM pasajeros pJOIN reservas r ON p.pasajero_id = r.pasajero_idGROUP BY p.pasajero_id, p.nombre, p.apellido, p.nacionalidadHAVING COUNT(DISTINCT r.clase) = ( SELECT COUNT(DISTINCT clase) FROM reservas)ORDER BY pasajero;