How to do multiple tasks in a single SQL query - mysql

I was given the database below:
movie(movie_id, movie_name, production_year, votes, ranking, rating)
movie_info(movie_id, movie_genre_id, note)
movie_genre(movie_genre_id, genre_name)
person(person_id, person_name, gender)
role(person_id, movie_id, role_name, role_type_id)
role_type(role_type_id, type_name)
I was asked to display the name of the top 7 directors with at least 3 movies in the list, the number of movies they are in and the average rating of their movies, sorted by the average rating. With the query below I managed to get the name of the directors, the number of movies they are in and the average rating, but I'm having issues limiting it to the top 7 and sorting them by the average rating. I tried using LIMIT and ORDER BY, but I'm getting syntax errors.
SELECT
person_name, COUNT(role.movie_id), AVG(rating)
FROM
movie
INNER JOIN
role
ON role.movie_id = movie.movie_id
INNER JOIN
person
ON role.person_id = person.person_id
INNER JOIN
role_type
ON role.role_type_id = role_type.role_type_id
WHERE
type_name = 'director'
GROUP BY
person_name
HAVING
COUNT(role.movie_id) > 2;
I can even order by the number of movies they did and limit it to the top 7, but for God I cannot order it by the AVG(rating)
person_name COUNT(role.movie_id) AVG(rating)
Hitchcock, Alfred 9 8.2888890372382
Kubrick, Stanley 8 8.2999999523163
Wilder, Billy 6 8.3000000317891
Spielberg, Steven 6 8.4000000953674
Scorsese, Martin 6 8.3166666030884
Nolan, Christopher 6 8.5333331425985
Tarantino, Quentin 6 8.3666666348775

In MySQL, Aliases defined in the Select clauses can be used in the Group By, Order By and Having clauses.
Use Order by .. DESC to sort the result-set in descending order and Limit 7 to get only 7 rows.
You should use proper Aliasing in multi table queries, to avoid ambiguous and unintended behavior.
You need to use Group By on person_id also, as there may be cases where director(s) have same name.
If you have duplicate entries in role table, you will have to use Count(Distinct ...) to avoid counting duplicate rows.
Try the following query:
SELECT
p.person_id,
p.person_name,
COUNT(r.movie_id) AS movies_count,
AVG(m.rating) AS average_rating
FROM
movie AS m
INNER JOIN
role AS r
ON r.movie_id = m.movie_id
INNER JOIN
person AS p
ON r.person_id = p.person_id
INNER JOIN
role_type AS rt
ON r.role_type_id = rt.role_type_id
WHERE
rt.type_name = 'director'
GROUP BY
p.person_id,
p.person_name
HAVING
movies_count > 2
ORDER BY
movies_count DESC,
average_rating DESC
LIMIT 7

Related

How to join information from 3 tables into 1 column?

I have 3 tables dogs, breeds and entries. I need to get AVG(score) from entries, Name from dogs and name from breeds.
I have managed to connect 2 tables together and I'm struggle to get the last thing (breeds.name) attached to the table
SELECT AVG(score), dogs.name, dogs.breed_id
FROM entries
JOIN dogs
ON entries.dog_id = dogs.id
JOIN breeds
ON breeds.id = dogs.breed_id
GROUP BY breeds.name, dogs.name, dogs.breed_id
having count(entries.dog_id) > 1
order by AVG(score) DESC
LIMIT 10
current result:
How do I change breed_id for breed.name instead?
Considering that you need name from breeds, name from dogs, and AVG(score) from entries, the following SQL should do that.
SELECT breeds.name, dogs.name, AVG(score)
FROM entries
JOIN dogs ON entries.dog_id = dogs.id
JOIN breeds ON breeds.id = dogs.breed_id
GROUP BY breeds.name, dogs.name
order by AVG(score) DESC
LIMIT 10
You have answered your own question within the question. All you need to do is replace dogs.breed_id with breeds.name. As this will leave you with two columns named name in your result set you should alias them to make it clearer.
Assuming that the id columns are defined as PRIMARY KEYs you can GROUP BY dogs.id (or entries.dog_id) as both of the non-aggregated columns (dogs.name and breeds.name) are functionally dependent on dogs.id.
SELECT AVG(e.score) avg_score, d.name dog_name, b.name breed_name
FROM entries e
JOIN dogs d ON e.dog_id = d.id
JOIN breeds b ON b.id = d.breed_id
GROUP BY d.id
HAVING COUNT(e.dog_id) > 1
ORDER BY avg_score DESC
LIMIT 10

Remove duplicates based on rank after join in SQL request

I am using MySQL 5.6.
I have a SQL table with a list of users:
id name
1 Alice
2 Bob
3 John
and a SQL table with the list of gifts for each user (numbered in order of preference):
id gift rank
1 balloon 2
1 shoes 1
1 seeds 3
1  video-game 1
2 computer 2
3 shoes 2
3 hat 1
And I would like a list of the preferred gift for each user (the highest rank - if two gifts have the same rank, pick only one randomly) (bonus: if the list could be randomized, that would be perfect!):
id name gift rank
2 Bob computer 2
1 Alice shoes 1
3 John hat 1
I tried to use the clause GROUP BY but without any success.
Considering rank as a part of your data; Without using window functions or complex sub queries
SELECT u.id, u.name, g.gift
FROM users u
JOIN gifts g ON g.id = u.id
LEFT JOIN gifts g2 ON g2.id = g.id AND g2.rank > g.rank
WHERE g2.id IS NULL;
Added link http://sqlfiddle.com/#!9/62f59e/15/0
You can use row_number to get one row for each User.(Mysql 8.0+)
SELECT A.ID,NAME,GIFT,`RANK` FROM USERS A
LEFT JOIN (
SELECT ID,GIFT,`RANK` FROM
(SELECT *,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY `RANK` ASC) AS RN FROM X) X
WHERE RN =1
) B
ON A.ID= B.ID
I do not know DB what you use. And I'm not an expert in SQL(I can have some mistake in next). But I think it is not difficult.
So I can give you just advice that you have to think gradually. Let me write.
First All I need is the highest rank. So I have to get this.
SELECT MAX(RANK)
FROM GIFT
GROUP BY ID
And then I think that I need get gifts from this rank.
SELECT GIFT.*
FROM GIFT
INNER JOIN(
SELECT ID, MAX(RANK)
FROM GIFT
GROUP BY ID
) filter ON GIFT.ID = filter.ID AND GIFT.RANK = filter.RANK
I think this is the table what you want!
So If below code works, That's what you really want.
SELECT *
FROM USER
LEFT OUTER JOIN(
above table
) GIFT ON USER.ID = GIFT.ID
But Remember this, I said I'm not an expert in SQL. There can be better way.
Checkout the query
SELECT tbluser.id,name,gift,rank into tblrslt
FROM tbluser
LEFT JOIN tblgifts
ON tbluser.id = tblgifts.id order by id,rank;
SELECT tt.*
FROM tblrslt tt
INNER JOIN
(SELECT id, min(rank) AS rank
FROM tblrslt
GROUP BY id) groupedtt
ON tt.id = groupedtt.id
AND tt.rank = groupedtt.rank order by id
In MySQL versions older than 8 you have no ranking functions available. You'll select the minimum rank per user instead and use these ranks to select the gift rows. This means you access the gifts table twice.
I suggest this:
select *
fron users u
join gifts g
on g.id = u.id
and (g.id, g.rank) in (select id, min(rank) from gifts group by id)
order by u.id;
If you also want to show users without gifts, simply change the inner join to a left outer join.

SQL finding most popular books

I have Books table
BookID BookName
1 BookA
2 BookB
3 BookC
Member table
MemberID MemberName
1 MemberA
2 MemberB
Borrow Table
MemberID BookID
1 1
1 2
2 1
2 2
I want to find out five popular book by Memeber A
I tried the following query
SELECT TOP (5) Book.BookTitle, COUNT(*) AS Count, Member_1.MemberName
FROM Book INNER JOIN
Borrow ON Book.BookID = Borrow.BookID INNER JOIN
Member ON Borrow.MemberID = Member.MemberID INNER JOIN
Member AS Member_1 ON Borrow.MemberID = Member_1.MemberID
where Member.MemberName='A'
GROUP BY Book.BookTitle, Member_1.MemberName
ORDER BY Count DESC
But this is not giving me the actual result.
Any suggestion would be appreciated.
I think you have too many joins:
SELECT TOP (5) b.BookTitle, COUNT(*) AS Count, m.MemberName
FROM Book b INNER JOIN
Borrow bo
ON bo.BookID = b.BookID INNER JOIN
Member m
ON bo.MemberID = m.MemberID
WHERE m.MemberName = 'A'
GROUP BY b.BookTitle, m.MemberName
ORDER BY Count DESC;
Note: This syntax is usually associated with SQL Server and does not work in MySQL. In MySQL, you would use LIMIT 5 rather then SELECT TOP (5).

How can I make a query retrieving highest rated movie?

I have the following tables:
Movie ( mID, title, year, director )
Reviewer ( rID, name )
Rating ( rID, mID, stars, ratingDate )
What i want to do is get the directors name along with the movies name which he has directed and got the highest rating.
For example, if Steven Spielberg has directed two movies (namely A and B) which have got 3 stars and 5 stars rating respectively, then the query must show Steven Spielberg and B (movie with the highest rating).
PS: I only need help with the approach. Hope I made myself clear. Please ask if any more info or explanation needed.
Why dont you try this,
SELECT TITLE,DIRECTOR FROM MOVIE,
(SELECT MAX(STARS),mID FROM RATING GROUP BY mID) R
WHERE MOVIE.mID=R.mID
Set up a subselect to get the director and the highest rating:-
SELECT director, MAX(stars)
FROM Movie
INNER JOIN Rating
ON Movie.mID = Rating.mID
INNER JOIN
(
SELECT director, MAX(stars) AS MaxRating
FROM Movie
INNER JOIN Rating
ON Movie.mID = Rating.mID
GROUP BY director
) Sub1
ON Movie.directort = Sub1.director
AND Rating.stars = Sub1.MaxRating
However I presume you will need more details. You do not appear to use the reviewer table at the moment, and I presume that one movie could have had several different reviewers who could have given different ratings. If so you would want to use the above as a subselect to join back against the rating table (macthign on the title and stars), and from that to the reviewer table.
Here you go
SELECT q.* FROM (SELECT m.*,MAX(r.`stars`) AS maxrating FROM `movie` m
INNER JOIN `rating` r ON (m.`mID` = r.`mID` )
GROUP BY r.`mID` ORDER BY maxrating DESC ) q GROUP BY q.director
ORDER BY q.maxrating DESC
And i am sure this question is taken from the quiz of DB class provided by stanford university
Here is your fiddle
Another way to do that is:
select m.title, max(r.stars) as stars
from rating r
inner join movie m on r.mid = m.mid
group by r.mid
order by m.title
this code should suffice :
select distinct m1.director, m1.title, r1.stars from movie m1
join rating r1 on m1.mID = r1.mID
left join (
select m2.director, r2.stars from movie m2
join rating r2 on m2.mID = r2.mID
) s on m1.director = s.director and r1.stars < s.stars
where s.stars is null and m1.director is not null;

count most occurences with the WHERE from other table

I have a movie DB to organize my collection but also I'm using it to learn more of mySQL. along the development I find bumps (plenty of them) and right now my problem is this:
Table ACTORS:
id_actor
name
sex
Table MOVIEACTORES:
id_movieactores
id_movie
id_actor
I want to count the TOP 5 (top10, top20 or whatever!) of actors with most movies and then the Top5 of actresses with most movies!
I have this:
SELECT filmesactores.id_actor,
COUNT( * ) AS contagem
FROM filmesactores
GROUP BY id_actor
ORDER BY contagem DESC
LIMIT 10
But this code doesn't discriminates actors from actresses. I feel the solution might be simple but with my knowledge is out of my reach right now. Anyone?
Grouping by sex, name would separate actors' counts by gender, but since you want to apply the limit to each gender group (i.e. top 5 actors and top 5 actresses), perform two queries and UNION their results together:
SELECT name, COUNT(*) AS moviecount
FROM actors
JOIN movieactores ON actors.id_actor = movieactores.id_actor
WHERE sex = 'Male'
GROUP BY name
ORDER BY COUNT(id_movie) DESC
LIMIT 5
UNION
SELECT name, COUNT(*)
FROM actors
JOIN movieactores ON actors.id_actor = movieactores.id_actor
WHERE sex = 'Female'
GROUP BY name
ORDER BY COUNT(id_movie) DESC
LIMIT 5