-- actores_por_peliculas: -- Todos los datos de la película y el total de actores que han trabajado -- drop view if exists actores_por_peliculas; -- en el caso de eliminar puedo usar el 'IF EXISTS' para asegurarme de que existe -- En caso contrario no hace nada -- En el caso de las vistas puedo usar OR REPLACE para que si existe -- La reemplace por la nueva definición create or replace view actores_por_peliculas as select film.*, count(actor_id) actores from `film` join film_actor on film.film_id=film_actor.film_id group by film_id; -- estadisticas_peliculas: el título de la película, la cantidad de veces que se ha alquilado, -- el importe total de pagos, la media de los pagos create or replace view estadisticas_peliculas as select title, count(rental_id) alquileres, sum(amount) total_pagos,avg(amount) media_pagos from film left join inventory using(film_id) left join rental using(inventory_id) left join payment using(rental_id) group by film_id; -- estadisticas_pais: El nombre del país, la cantidad de clientes que tiene, -- la cantidad de alquileres y la suma total de los pagos -- Usamos DISTINCT para que no nos cuenta varias veces el mismo cliente -- Usamos LEFT JOIN para que nos salgan los paises que no tengan clientes -- O que tengan clientes que no han alquilado nada create or replace view estadisticas_pais as select country, count(distinct customer.customer_id) clientes, count(rental_id) alquileres, sum(amount) pagos from country left join city using(country_id) left join address using(city_id) left join customer using(address_id) left join rental using(customer_id) left join payment using(rental_id) group by country_id; -- Cread una tabla 'peliculas_sin_alquilar' con -- toda la información de las películas que no se han alquilado ninguna vez -- En este caso mejor elimino la que existe para sustituir -- O la creo solo si no existe drop table if exists peliculas_sin_alquilar; create table if not exists peliculas_sin_alquilar select film.* from film left join inventory on film.film_id=inventory.film_id left join rental on inventory.inventory_id=rental.inventory_id where rental_id is null; -- Cread una tabla 'actores_comedia' con todos los actores que han trabajado en comedia (sin duplicados) drop table if exists actores_comedia; create table if not exists actores_comedia select actor.* from actor join film_actor using (actor_id) join film using (film_id) join film_category using (film_id) join category using (category_id) where name='comedy';
Categoría: BD
Duplicar tablas y copiar datos
-- Duplicar tablas o copiar datos en una tabla -- Con esta sentencia creamos una tabla nueva con la misma estructura que otra -- Copiando los datos CREATE TABLE nueva_tabla SELECT * FROM actor; -- Con esta sentencia copiamos los datos de una tabla en otra ya existente insert into nueva_tabla (first_name, last_name) select first_name,last_name from actor; -- A veces no creamos una vista sino una tabla porque queremos 'congelar' -- Un instante de tiempo o si el cálculo es muy costoso y quiero -- realizar consultas sobre ese resultado me sale a cuenta hacer -- el cálculo una vez y consultar muchas create table total_pagos select customer.*, sum(amount) pagos from customer join payment on customer.customer_id=payment.customer_id group by customer_id;
UNION
-- Consultas de union -- Nos permiten mezclar datos de tablas diferentes -- Si hay elementos repetidos los elimina -- Si quiero incluir los repetidos uso UNION ALL select first_name, last_name, 'actor' type from actor union select first_name, last_name, 'customer' type from customer union -- Puedo incluir cualquier valor siempre y cuando tengamos el mismo número de campos -- Pero que pueda no quiere decir que deba, esto no tiene ningún sentido select district, phone, 'address' type from address order by first_name,last_name; select title,first_name,last_name from film left join film_actor on film.film_id=film_actor.film_id left join actor on film_actor.actor_id=actor.actor_id union select title,first_name,last_name from film right join film_actor on film.film_id=film_actor.film_id right join actor on film_actor.actor_id=actor.actor_id;
Soluciones varias
-- Clientes de España o Argentina 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 in ('spain','argentina'); -- Ventas totales por empleado select staff.*, sum(amount) total from staff join payment on staff.staff_id=payment.staff_id group by staff.staff_id; -- Películas en las que han trabajado más de 10 actores select film.*, count(actor_id) actores from film join film_actor on film.film_id=film_actor.film_id group by film_id having actores>10; -- El título de la película que más se ha alquilado (en número de alquileres) -- Solución decente pero no correcta -- ¿Por qué? Porque si hay varias películas que tengan el mismo número -- de alquileres solo saldrá una select title, count(rental_id) alquileres from film join inventory on film.film_id=inventory.film_id join rental on inventory.inventory_id=rental.inventory_id group by film.film_id order by alquileres desc limit 1; -- ¿Cual sería la solución correcta? -- Buscar el máximo de alquileres -- Selecciona las películas que tengan ese número de alquileres select title, count(rental_id) alquileres from film join inventory on film.film_id=inventory.film_id join rental on inventory.inventory_id=rental.inventory_id group by film.film_id having alquileres=( select count(rental_id) total from film join inventory on film.film_id=inventory.film_id join rental on inventory.inventory_id=rental.inventory_id group by film.film_id order by total desc limit 1); -- El título de la película que más dinero ha dado (en suma de importe) -- Solución decente pero no del todo correcta select title, sum(amount) importe from film join inventory on film.film_id=inventory.film_id join rental on inventory.inventory_id=rental.inventory_id join payment on rental.rental_id=payment.rental_id group by film.film_id order by importe desc limit 1; -- SOlución teniendo en cuenta empates select title, sum(amount) importe from film join inventory on film.film_id=inventory.film_id join rental on inventory.inventory_id=rental.inventory_id join payment on rental.rental_id=payment.rental_id group by film.film_id having importe=( select sum(amount) importe from film join inventory on film.film_id=inventory.film_id join rental on inventory.inventory_id=rental.inventory_id join payment on rental.rental_id=payment.rental_id group by film.film_id order by importe desc limit 1); -- Los 5 actores que han trabajado en menos películas select actor.*, count(film_id) peliculas from actor join film_actor on actor.actor_id=film_actor.actor_id group by actor_id order by peliculas limit 5; -- Posible otra solución también imperfecta pero quizá más correcta select actor.*, count(film_id) peliculas from actor join film_actor on actor.actor_id=film_actor.actor_id group by actor.actor_id having peliculas<=( select max(peliculas) from ( select count(film_id) peliculas from actor join film_actor on actor.actor_id=film_actor.actor_id group by actor.actor_id order by peliculas limit 5) as temp ); -- Encontrar los clientes que hayan gastado más de 100 dólares select customer.*, sum(amount) total from customer join payment on customer.customer_id=payment.customer_id group by customer_id having total>100; -- Actores que no hayan trabajado en películas para niños o familiares -- Consulta de división: primero miro los que sí y luego los que no select * from actor where actor_id not in ( select actor_id 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 where name in ('children','family')); -- Actores que hayan trabajado en películas que no se hayan alquilado nunca select distinct actor.* from actor join film_actor on actor.actor_id=film_actor.actor_id where film_id not in ( select film.film_id from film join inventory on film.film_id=inventory.film_id join rental on inventory.inventory_id=rental.inventory_id); 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 left join inventory on film.film_id=inventory.film_id left join rental on inventory.inventory_id=rental.inventory_id where rental_id is null; -- Clientes que han hecho un total de pagos por encima de la media. -- Divide y vencerás -- Calculo el total de pagos po cliente select customer.*, sum(amount) pagos from customer join payment on customer.customer_id=payment.customer_id group by customer_id; -- calculo la media sobre esta consulta select avg(pagos) media from ( select customer.*, sum(amount) pagos from customer join payment on customer.customer_id=payment.customer_id group by customer_id) as temp; -- filtro los clientes cuyo total esté por encima de la media select customer.*, sum(amount) pagos from customer join payment on customer.customer_id=payment.customer_id group by customer_id having pagos>(select avg(pagos) media from ( select customer.*, sum(amount) pagos from customer join payment on customer.customer_id=payment.customer_id group by customer_id) as temp); -- con vistas create view pagos_por_cliente as select customer.*, sum(amount) pagos from customer join payment on customer.customer_id=payment.customer_id group by customer_id; select * from pagos_por_cliente where pagos>(select avg(pagos) media from pagos_por_cliente); -- Clientes que no han alquilado documentales (‘documentary’) -- Primero los que alquilan select * from customer 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='documentary'; -- Pues esos no, los otros select * from customer where customer_id not in ( select customer_id from customer 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='documentary');
Vistas
-- Vistas: Es una manera de dar nombre a una consulta -- Y nos permite utilizarla como si fuera una tabla más select film.*, name categoria from category join film_category on category.category_id=film_category.category_id join film on film_category.film_id=film.film_id; -- Utilizo mucho la consulta anterior porque hago consultas del tipo -- Películas de una categoría, categorías con películas de una duración -- Categorías de películas con un rating select film.*, name categoria 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'; -- Lo que nos proporciona el SQL son las vistas -- Creo una vista llamada 'peliculas' que incorpore lo anterior -- CREATE VIEW select * from peliculas where categoria='children'; select categoria,count(film_id) total from peliculas group by categoria; -- Actores que han trabajado en la categoría children select distinct first_name, last_name from peliculas where name='children'; -- Películas por actor select first_name, last_name, count(film_id) total from peliculas group by first_name, last_name; select * from actor where actor_id not in (select actor_id from peliculas where name='children'); -- Con SQL create view alquileres as 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;
Consultas de división
-- Consultas de división -- Son consultas en las que tenemos que buscar información -- en 'negativo'. Es decir, registros de una tabla que no -- tengan relación con registros de otra tabla -- ¿Hay algún actor que no haya trabajado en películas de comedia? -- La consulta siguiente NO nos responde la pregunta -- Nos dice los actores que han trabajado en películas que no son comedia select first_name,last_name,name from actor join film_actor using(actor_id) join film using(film_id) join film_category using(film_id) join category using(category_id) order by actor.actor_id; -- where name<>'comedy'; -- ¿Cual es el enfoque correcto? -- Darle la vuelta a la pregunta -- QUé actores sí que han trabajado en comedia select first_name,last_name,name from actor join film_actor using(actor_id) join film using(film_id) join film_category using(film_id) join category using(category_id) where name='comedy'; -- Uso la consulta como subconsulta y digo todos los actores que no están -- en la consulta anterior select * from actor where actor_id not in (select actor_id from actor join film_actor using(actor_id) join film using(film_id) join film_category using(film_id) join category using(category_id) where name='comedy'); -- ¿Hay actores que no han trabajado en películas de rating 'R' -- Esta consulta, igual que antes, no nos responde la pregunta -- Nos dice qué actores han trabajado en películas que no son de rating 'R' -- Eso no es lo que nos están preguntando select * from actor join film_actor using(actor_id) join film using(film_id) where rating<>'R'; -- Le damos la vuelta a la pregunta ¿Qué actores SÍ que han trabajado -- en películas de rating 'R' select * from actor join film_actor using(actor_id) join film using(film_id) where rating='R'; -- Una vez yo sé qué actores han trabajado en películas de rating R -- Los descarto, esos no select * from actor where actor_id not in ( select actor_id from actor join film_actor using(actor_id) join film using(film_id) where rating='R' );
Subconsultas
-- SUBCONSULTAS -- Poner consultas detro de otras consultas -- Se pueden poner en el WHERE, en el FROM y como campos -- El importe máximo de un pago en mi tabla de pagos select max(amount) from payment; -- Quiero saber los clientes que han hecho pagos por la cantidad máxima -- ¿Como lo puedo hacer? Usando una subconsulta -- En el where (y también puede ser en el having) -- No utilizo un valor fijo sino que uso otra consulta select distinct first_name, last_name from customer join payment on customer.customer_id=payment.customer_id where amount=(select max(amount) from payment); -- Puedo usar una subconsulta como un campo select distinct first_name, last_name, (select count(*) from rental where customer.customer_id=rental.customer_id) alquileres from customer; -- Puedo usar una subconsulta como 'tabla virtual' -- Es decir, yo creo una consulta y puedo seleccionar datos dentro de esa consulta -- Por ejemplo ¿Cual es la media de alquileres por cliente? -- Primero hago la consulta que me da el total de alquileres por cliente select first_name,last_name, count(rental_id) alquileres from customer join rental on customer.customer_id=rental.customer_id group by customer.customer_id; -- Después uso esa consulta como una subconsulta (subquery) select avg(alquileres) from (select count(rental_id) alquileres from customer join rental on customer.customer_id=rental.customer_id group by customer.customer_id) temp; -- ¿Qué clientes tienen alquileres por encima de la media? select first_name,last_name, count(rental_id) alquileres from customer join rental on customer.customer_id=rental.customer_id group by customer.customer_id having alquileres>(select avg(alquileres) from (select count(rental_id) alquileres from customer join rental on customer.customer_id=rental.customer_id group by customer.customer_id) temp); -- Que clientes han pagado por encima de la media -- Calcular el total de pago por cliente select first_name, last_name, sum(amount) total from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id; -- COn esto puedo calcular la media select avg(total) media from ( select sum(amount) total from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id) temp; -- después obtengo los clientes que superan esa media select first_name, last_name, sum(amount) total from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id having total>(select avg(total) media from ( select sum(amount) total from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id) temp);
GROUP_CONCAT
-- Concatenar valores de cadena -- GROUP_CONCAT select first_name,last_name,group_concat(title) peliculas from actor join film_actor on actor.actor_id=film_actor.actor_id join film on film.film_id=film_actor.film_id group by actor.actor_id; -- Puedo ordenar los valores select first_name,last_name,group_concat(title ORDER BY title) peliculas from actor join film_actor on actor.actor_id=film_actor.actor_id join film on film.film_id=film_actor.film_id group by actor.actor_id; -- Puedo cambiar el separador select first_name,last_name,group_concat(title ORDER BY title SEPARATOR ' | ') peliculas from actor join film_actor on actor.actor_id=film_actor.actor_id join film on film.film_id=film_actor.film_id group by actor.actor_id;
Limitar resultados (limit)
-- El cliente que más gasta select customer.*, sum(amount) gasto from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id order by gasto desc limit 1; -- el segundo que más gasta select customer.*, sum(amount) gasto from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id order by gasto desc limit 1,1; -- Paginación: limit 0,10 limit 10,10 limit 20,10 -- Se suele hacer desde el programa select customer.*, sum(amount) gasto from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id order by gasto desc limit 0,10; select customer.*, sum(amount) gasto from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id order by gasto desc limit 10,10; select customer.*, sum(amount) gasto from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id order by gasto desc limit 20,10;
Soluciones ejercicios agrupados
-- Mostrar los clientes ordenados por gasto total descendente -- Total de gasto por cliente select customer.*, sum(amount) gasto from customer join payment on customer.customer_id=payment.customer_id group by customer.customer_id order by gasto desc; -- Mostrar los países que tengan menos de 10 clientes (94) -- country - city - address - customer -- contar -- agrupado por pais select country, count(customer_id) clientes 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 group by country.country_id having clientes<10; -- Mostrar las películas que tengan 3 categorías o más -- film - film_category -- contar -- agrupo por film select film.*,count(category_id) categorias from film join film_category on film.film_id=film_category.film_id group by film.film_id having categorias>=3; -- Mostrar los actores que han trabajado en más de 20 películas (181) -- actor - film_actor -- contar -- agrupar por actor select actor.*, count(film_id) peliculas from actor join film_actor on actor.actor_id=film_actor.actor_id group by actor.actor_id having peliculas>20; -- Mostrar los actores que han trabajado en 5 o más películas de acción (5) -- actor - film_actor - film - film_category - category -- contar -- agrupar por actor select actor.*, count(film.film_id) peliculas from actor join film_actor using(actor_id) join film using(film_id) join film_category using(film_id) join category using(category_id) where name='Action' group by actor.actor_id having peliculas>=5;