How should I join these 3 SQL queries in Oracle? - sql

I have these 3 queries:
SELECT
title, year, MovieGenres(m.mid) genres,
MovieDirectors(m.mid) directors, MovieWriters(m.mid) writers,
synopsis, poster_url
FROM movies m
WHERE m.mid = 1;
SELECT AVG(rating) FROM movie_ratings WHERE mid = 1;
SELECT COUNT(rating) FROM movie_ratings WHERE mid = 1;
And I need to join them into a single query. I was able to do it like this:
SELECT
title, year, MovieGenres(m.mid) genres,
MovieDirectors(m.mid) directors, MovieWriters(m.mid) writers,
synopsis, poster_url, AVG(rating) average, COUNT(rating) count
FROM movies m INNER JOIN movie_ratings mr
ON m.mid = mr.mid
WHERE m.mid = 1
GROUP BY
title, year, MovieGenres(m.mid), MovieDirectors(m.mid),
MovieWriters(m.mid), synopsis, poster_url;
But I don't really like that "huge" GROUP BY, is there a simpler way to do it?

You could do something like this:
SELECT title
,year
,MovieGenres(m.mid) genres
,MovieDirectors(m.mid) directors
,MovieWriters(m.mid) writers
,synopsis
,poster_url
,(select avg(mr.rating)
from movie_ratings mr
where mr.mid = m.mid) as avg_rating
,(select count(rating)
from movie_ratings mr
where mr.mid = m.mid) as num_ratings
FROM movies m
WHERE m.mid = 1;
or even
with grouped as(
select avg(rating) as avg_rating
,count(rating) as num_ratings
from movie_ratings
where mid = 1
)
select title
,year
,MovieGenres(m.mid) genres
,MovieDirectors(m.mid) directors
,MovieWriters(m.mid) writers
,synopsis
,poster_url
,avg_rating
,num_ratings
from movies m cross join grouped
where m.mid = 1;

I guess I don't see the problem with having several GroupBy columns. That's a very common pattern in SQL. Of course, code clarity is often in the eye of the beholder.
Check the explain plans for the two approaches; my guess is you'll get better performance with your original version since it only needs to process the movie_ratings table once. But I haven't checked, and that will be somewhat data and installation dependent.

how about
SELECT
title, year, MovieGenres(m.mid) genres,
MovieDirectors(m.mid) directors, MovieWriters(m.mid) writers,
synopsis, poster_url,
(SELECT AVG(rating) FROM movie_ratings WHERE mid = 1) av,
(SELECT COUNT(rating) FROM movie_ratings WHERE mid = 1) cnt
FROM movies m
WHERE m.mid = 1;
or
SELECT
title, year, MovieGenres(m.mid) genres,
MovieDirectors(m.mid) directors, MovieWriters(m.mid) writers,
synopsis, poster_url,
av.av,
cnt.cnt
FROM movies m,
(SELECT AVG(rating) av FROM movie_ratings WHERE mid = 1) av,
(SELECT COUNT(rating) cnt FROM movie_ratings WHERE mid = 1) cnt
WHERE m.mid = 1;

Related

Use the count result from another query in where condition

I wonder how to combine two of my queries.
I have these 3 tables:
movies
movie_id PK
room_id FK
rooms
room_id PK
seats INTEGER
tickets
ticket_id PK
movie_id FK
In this simplified example, a movie only plays in a room and many tickets are sold for each movie.
I want to query which movies still have seats available.
For that I need to check
(room.seats - all tickets sold for that movie) > 0
If I do this, I get the total of tickets for each movie
SELECT movie_id, COUNT(*)
FROM tickets
GROUP BY movie_id;
And I would like to use that results in this query are condition
SELECT movie_id
FROM movies
JOIN rooms ON movies.room_id = rooms.room_id
WHERE (rooms.seats - [THE COUNT OF THE OTHER QUERY]) > 0
Does anyone if it is possible to achieve that?
Thank you in advance
I don't know how to combine two queries, it would be nice to understand how to achieve it
Lets assume, if data looks something like this
select
a.movie_id,
a.seats as "TOTAL_SEATS",
count(1) as "SEATS_SOLD",
a.seats - count(1) as "AVAILABLE_SEATS",
CASE when a.seats - count(1) > 0 then 'Y' else 'N' End as "SEATS_AVAILABLE"
from
(
select
m.id as movie_id,
r.seats,
t.id
from
movie m,
rooms r,
tickets t
where
m.room_id = r.id
and m.id = t.movie_id
) a
group by
a.movie_id,
a.seats
order by
movie_id asc;
Output of the Query:
If you want movie_id's specifically, for those seats that are available, then query will be as below:
select
b.movie_id
from
(
select
a.movie_id,
a.seats as "TOTAL_SEATS",
count(1) as "SEATS_SOLD",
a.seats - count(1) as "AVAILABLE_SEATS",
CASE when a.seats - count(1) > 0 then 'Y' else 'N' End as "SEATS_AVAILABLE"
from
(
select
m.id as movie_id,
r.seats,
t.id
from
movie m,
rooms r,
tickets t
where
m.room_id = r.id
and m.id = t.movie_id
) a
group by
a.movie_id,
a.seats
) b
where
b.SEATS_AVAILABLE = 'Y';
Output of the query will be:
Queries were tested on Oracle database with the above mentioned data.
SELECT movie_id
FROM movies
JOIN rooms ON movies.room_id = rooms.room_id
LEFT JOIN (SELECT movie_id, COUNT(*) AS C_M
FROM tickets
GROUP BY movie_id) t
ON movies.movie_id = t.movie_id and (rooms.seats - t.C_M) > 0
or : use a quantifier ALL,ANY
SELECT movie_id
FROM movies
JOIN rooms ON movies.room_id = rooms.room_id
WHERE (rooms.seats -
ANY (SELECT COUNT(*) OVER(PARTITION BY movie_id)
FROM tickets
WHERE tickets.movie_id =movies.movie_id)) > 0

Problems with SQL Joins on a assigment

I've got a question for my assignment
Data
Question: For all cases where the same reviewer rated the same movie twice and gave it a higher rating the second time, return the reviewer's name and the title of the movie.
Here's what I've tried. I joined all the tables.
select *
from Rating
join Reviewer on Rating.rID = Reviewer.rID
join Movie on Rating.mID = Movie.mID
But how to continue? If a Reviewer rated the same Movie and the last rating of this movie is higher than former, then I need to show this reviewer. But how to do it in SQL?
Join what you already had with Rating again, so that you can get all records where the reviewer is the same and the movie is the same, then filter only rows where a record with a later ratingDate has more stars.
In case the same reviewer did review 3 or more times, then use select distinct to remove duplicates
select distinct rev.name, m.title
from Rating r1
join Reviewer rev on rev.rID = r1.rID
join Movie m on m.mID = r1.mID
join Rating r2 on r1.rID = r2.rID and r1.mID = r2.mID
where r1.ratingDate < r2.ratingDate and r1.stars < r2.stars
Here is a way to do this..
I find out the count of (rid,mid) combinations which have exactly 2 (ie two reviews by same reviewer against the same movie) this shows up as the column cnt.
After which i find the latest rating by ranking the ratingdate in desc. Thus row_number=1 gets you the latest rating value
with data
as (
select count(*) over(partition by rt.rid,rt.mid) as cnt
,row_number() over(partition by rt.rid,rt.mid order by rt.ratindate desc) as rnk
,rw.name
,mov.title
from rating rt
join reviewer rw
on rt.rid=rw.rid
join movie mov
on mov.mid=rt.mid
)
select *
from data
where rnk=1
and cnt=2
For the cases a reviewer rated the same movie multiple times, you are interested in their first and second rating. (Possible further ratings, i.e. a reviewer rating a movie a third or fourth time etc., must get ignored.) So, number the rows (with ROW_NUMBER). Then see whether the second rating is higher than the first (by grouping by reviewer and movie and comparing both ratings). For the matches look up reviewer name and movie title, for which you'd normally use where (rid, mid) in ( subquery ), but SQL Server does not support IN clauses with tuples, so you'd inner join instead.
select r.name, m.title
from reviewer r
cross join movie m
join
(
select rid, mid
from
(
select *, row_number() over(partition by rid order by ratingdate) as rn
from Rating
) numbered
group by rid, mid
having max(rn) > 1
and any_value(case when rn = 1 then stars end) <
any_value(case when rn = 2 then stars end)
) matches on matches.rid = r.rid and matches.mid = m.mid
order by r.name, m.title;

Shorten a query

I have to write a query that would calculate number of tickets purchased consisting only of movie genre of that type. At the end, I have to return movie genre and number of tickets bought for that genre. I have written a query but I was wondering if it can be made shorter and more compact?
Following is the database scheme:
movies(movieId, movieGenre, moviePrice)
tickets(ticketId, ticketDate, customerId)
details(ticketId, movieId, numOfTickets)
Here is my query:
select m.genre, count(*)
from(select t.ticketId, m.genre
from(select d.ticketId
from(select m.genre, t.ticketId
from tickets t join details d on t.ticketId =
d.ticketId join movies m on d.movieId = m.movieId
group by m.genre, t.ticketId) d
group by d.ticketId
having count(*) = 1) as t join details d on t.ticketId =
d.ticketId join movies m on d.movieId = m.movieId
group by t.ticketId, m.genre) m
group by m.genre;
This runs on a database so I am only able to post sample output:
comedy 29821
action 27857
rom-com 19663
I see no reason to use the table tickets, because the results do not filter or aggregate by ticketDate or customerID. Thus, a shorter sql is
SELECT m.moviegenre,
Sum(d.numoftickets) as SumNum
FROM details d
LEFT JOIN movies m
ON d.movieid = m.movieid
GROUP BY m.moviegenre
HAVING SumNum > 0
ORDER BY m.moviegenre
added 3/28 am
I am not sure what is meant by Duplicates?? In table = details(ticketId, movieId, numOfTickets) ??
I would expect that ticketId is unique, so what would explain duplicates?
Is the same ticketId being printed twice, repeatedly??
Determine what number of ticketId are duplicates--
SELECT ticketId, count(*) as cnt
FROM details d
GROUP By ticketId
HAVING count(*) > 1
Determine what number of "details" rows are duplicates--
SELECT ticketId, movieId, numOfTickets, count(*) as cnt
FROM details d
GROUP By ticketId, movieId, numOfTickets
HAVING count(*) > 1
Then again, it may be that table = movies(movieId, movieGenre, moviePrice) is the one with duplicates??
Determine what number of movieId are duplicates--
SELECT movieId, count(*) as cnt
FROM movies m
GROUP BY movieId
HAVING count(*) > 1
Remove duplicates from details--
SELECT m.moviegenre,
Sum(d.numoftickets) as SumNum
FROM
(Select Distinct * From details) d
LEFT JOIN movies m
ON d.movieid = m.movieid
GROUP BY m.moviegenre
ORDER BY m.moviegenre

MAX(COUNT(*)) for each ID in a SQL query

I'm needing some help with a SQL query using PostgreSQL 9.4.
I need the most rented movies on each local, this is the data I'm asked to select
movie title
year
local id
number of rents
Tables:
rental(idMovie, idLocal, idClient)
movies(idMovie, title, description, year)
This is what I have done, but is not what i am asked to do.
SELECT m.tile, m.year, r.idLocal, COUNT(*) AS cont
FROM rental r, movies m
WHERE m.idMovie=r.idMovie
GROUP BY r.idLocal, m.title, m.year
ORDER BY COUNT(*) DESC;
If I understand your question correctly what you want is to show the most rented out movie(s) for every location.
If that is the case then you could use a window function like rank() which will assign a ranking number to all movies based on the number of rentals for each location. The where clause then filters out the highest ranking movies (there can of course be more than one that shares the top spot).
select m.title, m.year, r.idLocal, r.rents
from movies m
join (
select
idMovie,
idlocal,
count(idmovie) rents,
rank() over (
partition by idlocal
order by count(idmovie) desc
) rn
from rental
group by idMovie, idLocal
) r on m.idMovie = r.idMovie
where rn = 1
order by idlocal;
Sample SQL Fiddle
Try this...
SELECT r.idLocal, m.title, m.year, count(r.idMovie) as count_movie
FROM rental r, movies m
WHERE m.idMovie=r.idMovie
GROUP BY r.idLocal, m.title, m.year
ORDER BY count_movie DESC;
Or
SELECT r.idLocal, m.title, m.year, count(r.idMovie) as count_movie
FROM rental r, movies m
WHERE m.idMovie=r.idMovie
GROUP BY r.idLocal, m.title, m.year
ORDER BY count(r.title) DESC;

expanding sql query for selecting top two rows based on rating criteria?

I am doing a few exercises to get my sql basics up. I am stuck here and unable to make any progress further. I would really appreciate if I could get tips on how to break down complex query such as the following:
There are three tables:
Movie ( mID, title, year, director ) --
There is a movie with ID number mID, a title, a release year, and a director.
Reviewer ( rID, name ) -- The reviewer with ID number rID has a certain name.
Rating ( rID, mID, stars, ratingDate ) -- The reviewer rID gave the movie mID a number of stars rating (1-5) on a certain ratingDate.
The problem is :
For all cases where the same reviewer rated the same movie twice and gave it a higher rating the second time, return the reviewer's name and the title of the movie.
Here is my attempt:
select distinct temp1.ID FROM (select * FROM
(select rID, name,title twos FROM
(select r.rID , rev.name, m.title, count(*) as twos from reviewer rev
JOIN rating r on r.rID=rev.rID
JOIN movie m on m.mID=r.mID
GROUP BY rev.rID) counts where counts.twos=2) result, rating r
where result.rID=r.rID ORDER BY ratingDate DESC) TEMP temp1
INNER JOIN TEMP temp2
ON temp1.rId = temp2.rId AND temp1.ratingDate > temp2.ratingDate
WHERE temp1.stars > temp2.stars;
I build this query iteratively. but It did not give right solution. so I would like to know how to approach this kind of problem.
This is NOT homework.I am doing online tutorial from here.
Thank you
In SQL, it helps to think in sets. For example you could select the set of reviews for which an earlier review with a lower rating exists:
select Reviewer.name
, Movie.title
from Rating
join Reviewer
on Reviewer.rID = Rating.rID
join Movie
on Movie.mID = Rating.mID
where exists
(
select *
from Rating prev
where prev.mID = Rating.mID
and prev.rID = Rating.rID
and prev.ratingDate < Rating.ratingDate
and prev.stars < Rating.stars
)
That's a really nice course btw!
First, you get rows with duplicate mID and rID combination. And then JOIN that to Rating to see if the second star given is higher than of the first. And then JOIN to Movie and Reviewer for the reviewer name and movie title.
SELECT
rv.name,
m.title
FROM (
SELECT
rID, mID
FROM Rating
GROUP BY rID, mID
HAVING COUNT(*) = 2
)t
INNER JOIN Rating r
ON r.rID = t.rID
AND r.mID = t.mID
INNER JOIN Rating r2
ON r2.rID = r.rID
AND r2.mID = r.mID
AND r2.ratingDate > r.ratingDate
INNER JOIN Movie m
ON m.mID = r.mID
INNER JOIN Reviewer rv
ON rv.rID = r.rID
WHERE
r2.stars > r.stars