Help with (somewhat simple) MySQL JOIN - mysql

Consider I have three tables:
Movies (movie 1, movie 2, etc)
Categories (action, suspense, etc)
Movies_Categories (movie 1 -> action, movie 1 -> suspense, movie 2 -> suspense, etc)
How could I select only the movies that belong or don't belong to a specific category using only 1 query?
Thanks!

Belongs:
SELECT m.*
FROM movies m
INNER JOIN movies_categories mc
ON m.id = mc.movie_id
INNER JOIN categories c
ON c.id = mc.category_id
AND c.name = 'action';
Doesn't belong:
SELECT m.*
FROM movies m
LEFT OUTER JOIN (SELECT mc.movie_id
FROM movies_categories mc
INNER JOIN categories c
ON c.id = mc.category_id
AND c.name = 'action') mcx
ON m.id = mcx.movie_id
WHERE mcx.movie_id IS NULL

SELECT m.*,
IF(mc.movie_id IS NULL, 'doesnt belong', 'belongs')
FROM Movies m
LEFT JOIN Movies_Categories mc ON mc.movie_id = m.id
AND mc.category_id = (SELECT id
FROM categories
WHERE name = 'action')

Related

MySQL - Joins and Between

List all the actors who made a film during the 1950s and also in the 1980s.
When I try exclusively:
SELECT a.name
FROM movies AS m
JOIN castings AS c
JOIN actors AS a
ON m.id = c.movieid
AND c.actorid = a.id
WHERE (m.yr BETWEEN 1950 AND 1959)
or:
SELECT a.name
FROM movies AS m
JOIN castings AS c
JOIN actors AS a
ON m.id = c.movieid
AND c.actorid = a.id
WHERE (m.yr BETWEEN 1980 AND 1989)
I get results for both separate queries. However I get no rows when I combine these.
SELECT a.name
FROM movies AS m1
JOIN movies AS m2
JOIN castings AS c
JOIN actors AS a
ON m1.id = c.movieid
AND m2.id = c.movieid
AND c.actorid = a.id
AND m1.id < m2.id
WHERE (m1.yr BETWEEN 1950 AND 1959) AND (m2.yr BETWEEN 1980 AND 1989);
How can I find the names I'm looking for?
Starting from your existing query, a straight-forward approach is to use aggregation, and filter with a HAVING clause:
SELECT a.name
FROM movies AS m
JOIN castings AS c ON m.id = c.movieid
JOIN actors AS a ON c.actorid = a.id
GROUP BY a.id, a.name
HAVING
MAX(m.yr BETWEEN 1950 AND 1959) = 1
AND MAX(m.yr BETWEEN 1980 AND 1989) = 1
You can use a subquery in where clause.
SELECT
a.name
FROM
actors AS a
WHERE EXISTS
(SELECT
1
FROM
movies m,
castings c
WHERE c.actorid = a.id
AND m.id = c.movieid
AND (m.yr BETWEEN 1950
AND 1959)
LIMIT 1)
OR EXISTS
(SELECT
1
FROM
movies m,
castings c
WHERE c.actorid = a.id
AND m.id = c.movieid
AND (m.yr BETWEEN 1980
AND 1989)
LIMIT 1)
Or you can use Group By like the following while getting number of movies:
SELECT
a.name,
SUM(IF(m.yr BETWEEN 1950 AND 1959, 1, 0)) AS e1950, -- Number of Movies in 1950s
SUM(IF(m.yr BETWEEN 1980 AND 1989, 1, 0)) AS e1980 -- Number of Movies in 1980s
FROM movies AS m
JOIN castings AS c
JOIN actors AS a
ON m.id = c.movieid
AND c.actorid = a.id
GROUP BY a.name HAVING e1950 > 0 AND e1980 > 0;

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 - 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.

SQL query, three tables

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

Need help with SQL query

Current query
SELECT *
FROM employee AS E
INNER JOIN credential AS C ON (C.id = E.credentialId)
LEFT JOIN person AS P ON (C.personId = P.id)
I want to modify this query so that it I just SELECT the employees/credentials which have both loginroles 1 and 2 (loginrole.id).
Relevant tables
loginrole
id
name
credential_has_loginrole
id
credentialId
loginroleId
You can join a subquery, which will return the credential Id's that have both login roles.
SELECT E.*, C.*, P.*
FROM employee AS E
INNER JOIN (
SELECT credentialId
FROM credential_has_loginrole
WHERE loginroleId IN (1,2)
GROUP BY credentialId
HAVING COUNT(DISTINCT loginroleId) = 2
) g ON E.credentialId = g.credentialId
INNER JOIN credential AS C ON (C.id = E.credentialId)
LEFT JOIN person AS P ON (C.personId = P.id)
Update: As per comments, to find employees with either or:
SELECT E.*, C.*, P.*
FROM employee AS E
INNER JOIN (
SELECT credentialId
FROM credential_has_loginrole
WHERE loginroleId IN (1,2)
GROUP BY credentialId
) g ON E.credentialId = g.credentialId
INNER JOIN credential AS C ON (C.id = E.credentialId)
LEFT JOIN person AS P ON (C.personId = P.id)
Alternatively, you can use a JOIN together with DISTINCT:
SELECT DISTINCT E.*, C.*, P.*
FROM employee AS E
INNER JOIN credential AS C ON (C.id = E.credentialId)
INNER JOIN credential_has_loginrole chr
ON E.credentialId = chr.credentialId
AND chr.loginroleId IN (1,2)
LEFT JOIN person AS P ON (C.personId = P.id)
The Scrum Meister's answer is correct. You can alternatively use 2 join trees:
SELECT *
FROM employee AS E
INNER JOIN credential AS c
INNER JOIN credential_has_loginrole chl0 ON (c0.id=chl0.credentialId AND chl0.loginroleId=1)
INNER JOIN credential_has_loginrole chl1 ON (c0.id=chl1.credentialId AND chl1.loginroleId=2)
ON (c.id = E.credentialId)