SQL query, three tables - mysql

So let's same I'm trying to find actors who are in two movies together (for the purpose of a degrees of separation page). I have databases as such (this is just some made up data):
actors
id first_name last_name gender
17 brad pitt m
2 kevin bacon m
movies
id name year
20 benjamin button 2008
roles
a_id m_id role
17 20 Mr. Benjamin Button
So I want to return the names of the movies which both actors are in. I have the first and last names of two actors.
I'm having a lot of trouble getting this to work. What I'm having trouble with, specifically, is the SELECT part
SELECT name FROM movies JOIN . . .
I'm starting with first_name and last_name values for each

You must join twice:
SELECT m.name movie_name
FROM movies m join roles r1 on
r1.m_id = m.id join actors a1 on
r1.a_id = a1.id join roles r2 on
r2.m_id = m.id join actors a2 on
r2.a_id = a2.id
WHERE
a1.first_name = 'brad' and a1.last_name = 'pitt' and
a2.first_name = 'kevin' and a2.last_name = 'bacon'
Show all actor combinations per film:
SELECT m.name movie_name, a1.id actor1, a2.id actor2
FROM movies m join roles r1 on
r1.m_id = m.id join actors a1 on
r1.a_id = a1.id join roles r2 on
r2.m_id = m.id join actors a2 on
r2.a_id = a2.id
WHERE
a1.id < a2.id
The < ensures that each combination is only reported once.

select m.name,group_concat(concat_ws(' ',a.first_name,a.last_name) order by a.last_name) as actors
from actors as a
inner join roles as r on a.id = r.a_id
inner join movies as m on m.id = r.m_id
where r.a_id in (2,17)
group by r.m_id
having count(r.a_id) = 2
order by m.name

declare #FirstActorID int,
#SecondActorID int;
select m.[name]
from
movies m
inner join [roles] r1 on r1.m_id = m.id and r1.a_id = #FirstActorID
inner join [roles] r2 on r2.m_id = m.id and r2.a_id = #SecondActorID

Related

Correctly join multiple many-to-many tables - MySQL query

a seemingly generic SQL query really left me clueless.
Here's the case.
I have 3 generic tables (simplified versions here):
Movie
id | title
-----------------------
1 | Evil Dead
-----------------------
2 | Bohemian Rhapsody
....
Genre
id | title
-----------------------
1 | Horror
-----------------------
2 | Comedy
....
Rating
id | title
-----------------------
1 | PG-13
-----------------------
2 | R
....
And 2 many-to-many tables to connect them:
Movie_Genre
movie_id | genre_id
Movie_Rating
movie_id | rating_id
The initial challenge was to write a query which allows me to fetch movies that belong to multiple genres (e.g. horror comedies or sci-fi action).
Thankfully, I was able to find this solution here
MySQL: Select records where joined table matches ALL values
However, what would be the correct option to fetch records that belong to multiple many-to-many tables? E.g. rated R horror comedies. Is there any way to do so without subquery (or a single one only)?
One method uses correlated subqueries:
select m.*
from movies m
where (select count(*)
from movie_genre mg
where mg.movie_id = m.id
) > 1 and
(select count(*)
from movie_rating mr
where mr.movie_id = m.id
) > 1 ;
With indexes on movie_genre(movie_id) and movie_rating(movie_id) this probably has quite reasonable performance.
The above is possibly the most efficient method. However, if you wanted to avoid subqueries, one method would be:
select mg.movie_id
from movie_genres mg join
movie_ratings mr
on mg.movie_id = mr.movie_id
group by mg.movie_id
having count(distinct mg.genre_id) > 0 and
count(distinct mr.genre_id) > 0;
More efficient than the above is aggregating before the join:
select mg.movie_id
from (select movie_id
from mg_genres
group by movie_id
having count(*) >= 2
) mg join
(select movie_id
from mg_ratings
group by movie_id
having count(*) >= 2
) mr
on mg.movie_id = mr.movie_id;
Although you state that you want to avoid subqueries, the irony is that the version with no subqueries probably has the worst performance of these three options.
E.g. rated R horror comedies
You can join all the tables together, aggregate by movie and filter with a HAVING clause:
select m.id, m.title
from movies m
inner join movie_genre mg on mg.movid_id = m.id
inner join genre g on g.id = mg.genre_id
inner join movie_rating mr on mr.movie_id = m.id
inner join rating r on r.id = mr.rating_id
group by m.id, m.title
having
max(r.title = 'R') = 1
and max(g.title = 'Horror') = 1
and max(g.title = 'Comedy') = 1
You can also use a couple of exists conditions along with correlated subqueries:
select m.*
from movie m
where
exists (
select 1
from movie_genre mg
inner join genre g on g.id = mg.genre_id
where mg.movie_id = m.id and g.title = 'R')
and exists (
select 1
from movie_rating mr
inner join rating r on r.id = mr.rating_id
where mr.movie_id = m.id and r.title = 'Horror'
)
and exists (
select 1
from movie_rating mr
inner join rating r on r.id = mr.rating_id
where mr.movie_id = m.id and r.title = 'Comedy'
)

how to get the values that fullfil the statement in my count?

I try to display each pair of actors, the two actors have not played on anyone
common movie genre while at the same time the genre that one has played together with the genre which has been played by the other being at least 7
I did this:
select a1.actor_id as i8opoios1,a2.actor_id as i8opoios2,((count(distinct(g1.genre_name))+count(distinct(g2.genre_name)))>=7) as result from actor as a1
inner join actor as a2 on a1.actor_id!=a2.actor_id
inner join role as r1 on a1.actor_id=r1.actor_id
inner join movie as m1 on m1.movie_id=r1.movie_id
inner join movie_has_genre as mg1 on mg1.movie_id=m1.movie_id
inner join genre as g1 on mg1.genre_id=g1.genre_id
inner join role as r2 on a2.actor_id=r2.actor_id
inner join movie as m2 on m2.movie_id=r2.movie_id
inner join movie_has_genre as mg2 on mg2.movie_id=m2.movie_id
inner join genre as g2 on mg2.genre_id=g2.genre_id
where a1.actor_id<a2.actor_id and mg1.genre_id!=mg2.genre_id
group by a1.actor_id,a2.actor_id;
This query returns me all the pair of actors who have not played on anyone
common movie genre and as a result a 1(TRUE) if combined they played on 7 or more genre and 0(FAlSE) if they hadnt.My question is if anyone has an idea on how can i return only the true statements.
Tables and their columns:
actor(actor_id,name)
role(actor_id,movie_id)
movie(movie_id,title)
movie_has_genre(movie_id,genre_id)
genre(genre_id,gender_name)
Add the condition to your where clause to limit the rows.
SELECT
a1.actor_id as i8opoios1,
a2.actor_id as i8opoios2,
IF((count(distinct(g1.genre_name))+count(distinct(g2.genre_name)))>=7,1,0) as result
FROM actor as a1
INNER JOIN actor as a2
on a1.actor_id != a2.actor_id
INNER JOIN role as r1
on a1.actor_id = r1.actor_id
INNER JOIN movie as m1
on m1.movie_id = r1.movie_id
INNER JOIN movie_has_genre as mg1
on mg1.movie_id = m1.movie_id
INNER JOIN genre as g1
on mg1.genre_id = g1.genre_id
INNER JOIN role as r2
on a2.actor_id = r2.actor_id
INNER JOIN movie as m2
on m2.movie_id = r2.movie_id
INNER JOIN movie_has_genre as mg2
on mg2.movie_id = m2.movie_id
INNER JOIN genre as g2
on mg2.genre_id = g2.genre_id
WHERE a1.actor_id < a2.actor_id
AND mg1.genre_id != mg2.genre_id
HAVING IF((count(distinct(g1.genre_name))+count(distinct(g2.genre_name)))>=7,1,0) = 1
GROUP BY a1.actor_id,a2.actor_id;

SQL Query for multiple relationships to exist in a many to many JOIN

I have a many to many relationship between Movies and Genres. What I want to do is query for Action Comedy Movies. This is as close as I have gotten:
SELECT * FROM movies
JOIN movies_genre ON (movies.id = movies_genre.movie_id)
JOIN genres ON (movies_genre.genre_id = genres.id)
WHERE (
genres.genre = "Comedy" OR
genres.genre = "Action & Adventure"
)
But this gives me all the movies that are Comedy or Adventure. If I change the OR to an AND then I get back an empty table. Is there a simple way to do this with one query?
You want information about a movie, so SELECT * is not appropriate. The following query returns movie ids that match both genres:
SELECT mg.movie_id
FROM movies_genre mg JOIN
genres g
ON mg.genre_id = g.id
WHERE g.genre IN ('Comedy', 'Action & Adventure')
GROUP BY mg.movie_id
HAVING COUNT(*) = 2;
Notes:
Table aliases make the query much easier to write and to read.
IN is more sensible than a bunch of OR expressions.
The HAVING clause counts the number of matching genres. It assumes that genres are not repeated.
If you want full movie information, you can join that in using additional logic.
Try...
select * from movies m
where m.id in (select movie_id from movies_genre join genres on (movies_genere.genre_id = genres.id) where genres.genre = 'Comedy')
and m.id in (select movie_id from movies_genre join genres on (movies_genere.genre_id = genres.id) where genres.genre = 'Action & Adventure')
Ugh, many-to-many relationships are the worst, requiring queries like this:
select *
from movies as m
where exists (
select 1
from movies_genre as mg
inner join genres as g
on g.id = mg.genre_id
where mg.movie_id = m.id
and g.genre in ('Action & Adventure', 'Comedy')
group by g.id
having count(*) = 2
)
I found something that works, but in no way do I believe it is the best way:
SELECT * FROM movies
JOIN movies_genre ON (movies.id = movies_genre.movie_id)
JOIN genres ON (movies_genre.genre_id = genres.id)
WHERE (
genres.genre = "Action"
AND (
movies.poster IN (
SELECT movies.poster FROM movies
JOIN movies_genre ON (movies.id = movies_genre.id)
JOIN genres ON (movies_genre.id = genres.id)
WHERE genres.genre = "Comedy"
)
)
)

SQL - List attributes that showed up with others

I am working on an assignment that requires me to list the actors and actresses that show up in a movie, and then the actors and actresses that have starred with them in other movies. A diagram of the database is viewable here: http://i.imgur.com/kj8qVgF.png
I have a query for the first part (getting the names of actors and actresses that show up in a certain movie).
SELECT DISTINCT n.name
FROM cast_info c
INNER JOIN name n
ON (n.id = c.person_id)
INNER JOIN title t
ON (c.movie_id = t.id)
CROSS JOIN role_type r
WHERE (t.title = 'The Movie') AND (r.role = 'actress' OR r.role = 'actor')
Could I get some assistance to help me find the actresses and actors that star with them in other movies?
Example:
Actors in a given movie 'The Movie': Bob, Joe, Billy
Actors in a different movie 'Another Movie': Joe, Daniel, Frank
Actors in another different movie 'Third Movie': Billy, Susan, Theodore
It should return Daniel, Frank, Susan, and Theodore, because they starred in at least one movie with one of the actors in the given movie.
According to your example, and your model is 3NF, so you can use trivial IN to solve the problem just like you described in Natural language.
SELECT DISTINCT n.name
FROM cast_info c
INNER JOIN name n
ON (n.id = c.person_id)
INNER JOIN title t
ON (c.movie_id = t.id)
INNER JOIN role_type r
ON (c.role_id = r.id)
WHERE (r.role = 'actress' OR r.role = 'actor') -- find the actresses and actors ...
AND c.movie_id IN -- starred in at least one movie ...
(SELECT movie_id
FROM cast_info
WHERE id in -- with one of the actors in the given movie => Bob, Joe, Billy
(SELECT cc.id
FROM cast_info cc
INNER JOIN role_type rr
ON (cc.role_id = rr.id)
WHERE ( rr.role = 'actor') -- only select actors in the given movie
AND cc.movie_id in (select id from title where title = 'The Movie')
)
)
AND c.id NOT IN -- except the actors in the given movie
(SELECT cc.id
FROM cast_info cc
INNER JOIN role_type rr
ON (cc.role_id = rr.id)
WHERE ( rr.role = 'actor') -- only select actors in the given movie
AND cc.movie_id in (select id from title where title = 'The Movie')
)
This easy to translate Natural language to SQL but not efficient using IN, you can transfer IN with EXISTS, that would be more efficient.
So we meet again. Hopefully I can be of the same assistance as your last question. I like that you began by breaking this down into smaller parts. I would have started at the same place as you - getting all of the actors in the necessary movie. I've changed your query slightly, by selecting the persons id instead of their name so I can keep this answer a little shorter and cleaner.
SELECT c.person_id
FROM cast_info c
JOIN title t ON c.movie_id = t.id
JOIN role_type r ON c.role_id = r.id
WHERE t.title = 'The Movie' AND (r.role = 'actress' OR r.role = 'actor');
*Note, I did not use distinct here because a person should not appear in this movie as an actor or actress more than once, but c.person_id is not a unique key so it would not hurt to be sure.
Then we can get all of the movies with those actors in them. We can filter to avoid the original table.
SELECT t.id
FROM title t
JOIN cast_info c ON c.movie_id = t.id
WHERE t.title != 'The Movie'
AND c.person_id IN(SELECT c.person_id
FROM cast_info c
JOIN title t ON c.movie_id = t.id
JOIN role_type r ON r.id = c.role_id
WHERE t.title = 'The Movie' AND (r.role = 'actress' OR r.role = 'actor'))
Now we can pull all of the actors from all of those movies, and exclude our original actors. Here is where it is a good idea to use distinct person id. This is because Bob may star in 'The Movie'. Later, Bob did a movie with John, and another movie with John, but we don't want John to appear twice.
So, here is the final query:
SELECT DISTINCT c.person_id
FROM cast_info c
WHERE c.movie_id IN(SELECT t.id
FROM title t
JOIN cast_info c ON c.movie_id = t.id
WHERE t.title != 'The Movie'
AND c.person_id IN(SELECT c.person_id
FROM cast_info c
JOIN title t ON c.movie_id = t.id
JOIN role_type r ON r.id = c.role_id
WHERE t.title = 'The Movie' AND (r.role = 'actress' OR r.role = 'actor')))
AND c.person_id NOT IN(SELECT c.person_id
FROM cast_info c
JOIN title t ON c.movie_id = t.id
JOIN role_type r ON r.id = c.role_id
WHERE t.title = 'The Movie' AND (r.role = 'actress' OR r.role = 'actor'))
As I've said before, it's difficult to test this without data, and it's a lot of tables to make a simple SQL Fiddle, so please try these in bits like I have written them if they don't work, and let me know what may need to be tweaked.

Joining tables in sql and obtain the data as per the query

I have three tables( movie,actor,casting). I want to know the actors name for the id obtained from this query.
select id from movie where title ='Casablanca';
My tables:
Movie | Actor | casting
_______ ________ _______
Movieid Actorid Movieid
title name Actorid
yr ord
director
budget
gross
This should do it:
SELECT a.name
FROM movie m
INNER JOIN casting c
ON m.id = c.movieid
INNER JOIN actor a
ON c.actorid = a.id
WHERE m.title = 'Casablanca';
Try this:
SELECT a.id, a.name
FROM actor a
INNER JOIN casting c
ON a.id = c.actorid
INNER JOIN movie m
ON c.movieid = m.id
WHERE m.title ='Casablanca';