-- Los clientes que han gastado más de 150 dolares -- En una consulta normal para filtrar uso WHERE -- En una consulta agrupada uso HAVING select first_name,last_name,sum(amount) total, avg(amount) media, min(amount) minimo,max(amount) maximo from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id HAVING total>=150 -- Ponemos una condición sobre los totales order by first_name,last_name; select first_name,last_name,sum(amount) total, avg(amount) media, min(amount) minimo,max(amount) maximo from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id HAVING total>=150 and media>5 -- Condición compuesta order by first_name,last_name; -- Resumiendo, HAVING nos permite poner condiciones sobre los totales -- de las funciones de agregado -- ¡OJO! HAVING es como un WHERE para los totales -- No podemos usar WHERE para los totales ni HAVING para los campos select first_name,last_name,sum(amount) total, avg(amount) media, min(amount) minimo,max(amount) maximo from customer join payment on customer.customer_id=payment.customer_id WHERE total>=150 -- Da error columna no encontrada group by customer.customer_id order by first_name,last_name; select first_name,last_name,sum(amount) total, avg(amount) media, min(amount) minimo,max(amount) maximo from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id HAVING amount<9 -- Da el mismo tipo de error order by first_name,last_name; -- Esto no quiere decir que no podamos tener condiciones compuestas -- que usen WHERE y HAVING -- Quiero saber el total, media, etc de los clientes cuyo nombre -- empieza por 'A' y el total es mayor de 150 -- Como construyo esto: pongo el select group by select first_name,last_name,sum(amount) total, avg(amount) media, min(amount) minimo,max(amount) maximo from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id order by first_name,last_name; -- La condiciones de las columnas no calculadas con WHERE select first_name,last_name,sum(amount) total, avg(amount) media, min(amount) minimo,max(amount) maximo from customer join payment on customer.customer_id=payment.customer_id WHERE first_name like 'a%' group by customer.customer_id order by first_name,last_name; -- La condiciones de las columnas calculadas con HAVING select first_name,last_name,sum(amount) total, avg(amount) media, min(amount) minimo,max(amount) maximo from customer join payment on customer.customer_id=payment.customer_id WHERE first_name like 'a%' group by customer.customer_id HAVING total>150 order by first_name,last_name;
Categoría: BD
Resolver consulta agrupada por pasos
-- Actores con mayor número de películas -- Total de películas por actor -- Primero me hago mi sentencia sql normal select * from actor join film_actor on actor.actor_id=film_actor.actor_id join film on film_actor.film_id=film.film_id; -- Segundo paso: ¿Qué tengo que hacer, sumar,contar...? -- En este caso voy a contar (COUNT) select first_name,last_name, count(film.film_id) total from actor join film_actor on actor.actor_id=film_actor.actor_id join film on film_actor.film_id=film.film_id; -- Tercer paso: ¿Por qué valor agrupo? -- Agrupamos por actor que es de quien queremos saber el número de películas select first_name,last_name, count(film.film_id) total from actor join film_actor on actor.actor_id=film_actor.actor_id join film on film_actor.film_id=film.film_id group by actor.actor_id; -- Cuarto paso: ¿Necesito ordenar por algo? -- ordeno por cantidad de películas descendente select first_name,last_name, count(film.film_id) total from actor join film_actor on actor.actor_id=film_actor.actor_id join film on film_actor.film_id=film.film_id group by actor.actor_id -- Siempre que tenemos joins el campo id puede estar en varias tablas, necesitamos indicarle la tabla que queremos order by total desc;
GROUP BY
-- Las funciones de agregado tienen sentido cuando agrupamos -- los valores: GROUP BY -- La sintaxis es select valor, agregado(..) from tabla1 join tabla2 GROUP BY valor -- Cuantas ciudades tiene cada pais select country,count(city) ciudades from country join city on country.country_id=city.country_id group by country; -- Total de pagos por cliente select first_name,last_name,sum(amount) total, avg(amount) media, min(amount) minimo,max(amount) maximo from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id order by first_name,last_name; -- Total de películas por categoría select name,count(film_id) total from category join film_category on category.category_id=film_category.category_id group by category.category_id order by total desc;
Funciones de agregado
-- Funciones de agregado: Calcular totales, medias,... -- Contando el número de actores de la tabla actor (COUNT) select count(*) total from actor; -- Sumar valores (SUM) select sum(amount) total from payment; -- Obtener una media (AVG) select avg(amount) media from payment; -- Obtener máximo y mínimo (MIN,MAX) select min(amount) minimo, max(amount) maximo from payment;
Ejemplo join largo de diferentes maneras
-- En qué paises se han alquilado películas de comedia -- ¿En que tablas está la información? -- Pais en country -- comedia en category -- Pues tengo que buscar el camino: -- country--city--address--customer--rental--inventory--film--film_category--category -- Formato estándar select distinct country from country join city on country.country_id=city.country_id join address on city.city_id=address.city_id join customer on address.address_id=customer.address_id join rental on customer.customer_id=rental.customer_id join inventory on rental.inventory_id=inventory.inventory_id join film on inventory.film_id=film.film_id join film_category on film.film_id=film_category.film_id join category on film_category.category_id=category.category_id where name='comedy' order by country; -- optimizado para mysql select * from country join city using (country_id) join address using (city_id) join customer using (address_id) join rental using (customer_id) join inventory using (inventory_id) join film using (film_id) join film_category using (film_id) join category using (category_id) where name='comedy' order by country; -- Formato estándar usando alias select distinct country from country c join city ci on c.country_id=ci.country_id join address a on ci.city_id=a.city_id join customer cu on a.address_id=cu.address_id join rental r on cu.customer_id=r.customer_id join inventory i on r.inventory_id=i.inventory_id join film f on i.film_id=f.film_id join film_category fc on f.film_id=fc.film_id join category ca on fc.category_id=ca.category_id where name='comedy' order by country;
Soluciones SQL
-- Películas que duren entre 100 y 120 minutos o entre 50 y 70 minutos select * from film where length >= 100 and length<=120 OR length between 50 and 70 order by length; -- Buscar todos los clientes (customer) de paises que empiecen por ‘A’ (27) select customer.* from country join city on country.country_id=city.country_id join address on city.city_id=address.city_id join customer on address.address_id=customer.address_id where country like 'a%'; -- Buscar todos los actores que hayan trabajado en películas de una longitud (length) mayor de 140 (200) select distinct actor.* from actor join film_actor on actor.actor_id=film_actor.actor_id join film on film_actor.film_id=film.film_id where length>140; -- Categorías con películas de rating ‘R’ (solo el nombre) (16) select distinct name from category join film_category on category.category_id=film_category.category_id join film on film_category.film_id=film.film_id where rating='R'; -- Películas para niños (children) o familiares (Family) (129) select distinct title from category join film_category on category.category_id=film_category.category_id join film on film_category.film_id=film.film_id where name='children' or name='family';
Resumen JOIN
-- Relacionamos los datos de diferentes tablas con JOIN -- Lo que hacemos es seguir el camino de las relaciones -- Que nos vienen del modelo ER -- Estas relaciones se implementan con los ids -- Por lo que para hacer una consulta que relacione varias tablas hacemos: -- 1) Identificar en que tablas tenemos la información que queremos -- 2) Ver el camino entre esas tablas -- 3) Identificar los ids de esas relaciones -- Ejemplo: Direcciones de España -- 1) La info la tengo en country y address -- 2) El camino es country--city--address -- 3) country-city: country_id city-address: city_id select * from country join city on country.country_id=city.country_id join address on city.city_id=address.city_id; -- El JOIN es fundamental dentro de las consultas de las bases de datos -- Porque muy pocas veces querremos información de solo una tabla -- A los campos y tablas les puedo poner alias select country pais, address direccion from country c join city ci on c.country_id=ci.country_id join address a on ci.city_id=a.city_id; -- Generalmente para modificar el nombre que vemos de los campos y en el caso -- de las tablas simplificar los joins -- JOIN me muestra los registros que están relacionados, lo que no tienen -- relación no salen. Si hay paises que no tienen ciudades o actores que -- no tienen películas no saldrán en el JOIN -- Para eso tenemos que usar LEFT o RIGHT para obtener TODOS los registros -- de la tabla que está a la derecha o a la izquierda aunque -- no tenga registros relacionados select * from country left join city on country.country_id=city.country_id; -- En la consulta anterior salen todos los paises incluyendo aquellos -- que no tengan ciudades
LEFT / RIGHT joins
-- JOIN por defecto solo nos muestra valores que están relacionados -- Si tenemos registros que no tienen elementos relacionados no salen -- Si yo quiero que sí que salgan utilizo LEFT / RIGHT -- Ejemplo: Todos los países que no tengan ciudades SELECT country, city FROM country left join city on country.country_id=city.country_id where city is null; select country,city from city right join country on country.country_id=city.country_id; -- Actores que no han trabajado en ninguna película select * from actor left join film_actor on actor.actor_id=film_actor.actor_id where actor.actor_id is null; -- Los ejemplos anteriores solo funcionan si introducimos datos nuevos -- Porque la BD de Sakila está cuadrada, no hay 'huecos' INSERT INTO actor (first_name,last_name) values ('Ana','Pi')
Alias
-- Alias: a los campos y a las tablas les podemos poner alias select first_name nombre, last_name apellidos from actor; select first_name as nombre, last_name as apellidos from actor; -- Se usa mucho en las tablas para simplificar los joins select name categoria, title pelicula from category c join film_category fc on c.category_id=fc.category_id join film f on fc.film_id=f.film_id;
Ejemplos JOIN
-- La sintaxis es tabla1 JOIN tabla2 ON tabla1.id=tabla2.id -- Un join de una relación 1 a N select country,city from country JOIN city ON country.country_id=city.country_id; -- Esta sintaxis no es standard del sql, solo sirve para mysql si -- el nombre del id es igual en las dos tablas select country,city from country JOIN city using(country_id); -- Un join de una relación N a N select name,title from category join film_category on category.category_id = film_category.category_id join film on film_category.film_id=film.film_id; -- Sintaxis alternativa select name,title from category join film_category using(category_id) join film using(film_id); -- Quiero ver los clientes de Spain select * from country join city on country.country_id=city.country_id join address on city.city_id=address.city_id join customer on address.address_id=customer.address_id where country='spain'; -- Alternativa select * from country join city using(country_id) join address using(city_id) join customer using(address_id) where country='spain'; -- ¿En qué películas ha trabajado el actor de id 1? select first_name,last_name,title from actor join film_actor on actor.actor_id=film_actor.actor_id join film on film_actor.film_id=film.film_id where actor.actor_id=1 order by title desc; select first_name,last_name,title from film join film_actor on film.film_id=film_actor.film_id join actor on film_actor.actor_id=actor.actor_id where actor.actor_id=1; -- Que actores han trabajado en películas de la categoría 'Comedy' -- Uso DISTINCT para evitar que salgan valores repetidos -- Si un actor ha trabajado en varias películas saldrá varias veces -- En order by ordeno por dos campos, first_name y last_name -- Para que si el nombre sea el mismo en diferentes actores -- Se ordene por el apellido select distinct name,first_name, last_name from category join film_category on category.category_id=film_category.category_id join film on film_category.film_id=film.film_id join film_actor on film.film_id=film_actor.film_id join actor on film_actor.actor_id=actor.actor_id where name='comedy' order by first_name, last_name;