is there a more efficient alternative for this SQL query? - sql

Im working on a movie data set that has tables for movies, genre and a bridge table in_genre.
The following query tries to find common genres between two movies. Im doing two joins to get the genre list and a intersect to find common genres.
Is there a more efficient way?
Table schema:
movie : movie_id(PK)(int)
in_genre(bridge_table): movie_id(FK)(int), genre_id(int)
SELECT count(*) as common_genre
FROM(
// getting genres of first movie
SELECT in_genre.genre_id
FROM movie INNER JOIN in_genre ON movie.id = in_genre.movie_id
WHERE movie.id = 0109830
INTERSECT
// getting genres of second movie
SELECT in_genre.genre_id
FROM movie INNER JOIN in_genre ON movie.id = in_genre.movie_id
WHERE movie.id = 1375666
) as genres

If it only needs the data from in_genre then there's no need to join the movie table.
And you can use an EXISTS to find the common genres.
SELECT COUNT(DISTINCT genre_id) as common_genre
FROM in_genre ig
WHERE movie_id = 0109830
AND EXISTS
(
SELECT 1
FROM in_genre ig2
WHERE ig2.movie_id = 1375666
AND ig2.genre_id = ig.genre_id
)

If you want the genres, I would simply do:
SELECT genre_id as common_genre
FROM in_genre ig
WHERE movie_id IN (0109830, 1375666)
GROUP BY genre_id
HAVING COUNT(*) = 2;
If you want the count, a subquery is simple enough:
SELECT COUNT(*)
FROM (SELECT genre_id as common_genre
FROM in_genre ig
WHERE movie_id IN (0109830, 1375666)
GROUP BY genre_id
HAVING COUNT(*) = 2
) g;
If you want full information about the genres, then I would suggest exists:
select g.*
from genres g
where exists (select 1
from in_genre ig
where ig.genre_id = g.genre_id and ig.movie_id = 0109830
) and
exists (select 1
from in_genre ig
where ig.genre_id = g.genre_id and ig.movie_id = 1375666
);

Related

MS Access SQL - Left Join a table that only has unique matches

I'm using an example database I got through DBeaver. It contains a table called Artists and a table called Albums. Some artists have no albums, some have one and some have many.
What I'm trying to achieve is a left join to show all artists but only display the album name of the artists who only have one album.
The following works using DBeaver but doesn't work if executed directly from MS Access (which i need to be able to do):
SELECT
ar.AName,
al.title
FROM
Artist ar
LEFT JOIN (
SELECT
al.*
FROM
album al
INNER JOIN (
SELECT
artistid,
COUNT( artistid ) artistCount
FROM
album
GROUP BY
artistid ) alc ON
alc.artistid = al.artistid
AND alc.artistcount = 1 ) al ON
al.artistid = ar.artistid
ORDER BY
ar.aname
Tables have the following columns:
Artist
ArtistID (Key)
Name
Album
AlbumID (Key)
Title
ArtistID
Many thanks in advance. I can't find an answer to this anywhere. I understand the version of SQL used in MS Access isn't the same as found in various other variants.
Cheers
Patrick
I would go with NOT EXISTS :
SELECT a.ArtistID, a.Name, al.Title
FROM Artist a LEFT JOIN
Album al
ON al.ArtistID = a.ArtistID AND
NOT EXISTS (SELECT 1
FROM Album al1
WHERE al1.ArtistID = al.ArtistID AND a1l.AlbumID <> al.AlbumID
);
Thank you for your help. I have managed to produce the results I was expecting using SQL in MS Access (will need to test to ensure its 100%).
I used the following:
SELECT
ar.AName,
al.title
FROM
Artist ar
LEFT JOIN (
SELECT
al.*
FROM
album al,
(
SELECT
al1.artistid,
COUNT( al1.artistid ) AS al1Count
FROM
album al1
GROUP BY
al1.artistid ) al1
WHERE
al.artistID = al1.artistid
AND al1.al1count = 1 ) al ON
al.artistid = ar.artistid
ORDER BY
ar.aname
Thanks again!
You don't want a left join. If an artist has exactly one album, then a regular join will work. The key is aggregation:
select ar.aname, max(al.title) as title
from artist as ar inner join
album as al
on ar.artistid = al.artistid
group by ar.artistid, ar.aname -- I supposed two artists could have the same name
having count(*) = 1;
The key idea is that if there is only one album, then max(al.title) returns that album's name.
EDIT: (based on comment)
select ar.aname,
switch(al.artistid is null, "No album"
count(*) = 1, max(al.title),
1=1, "> 1"
) as album_title
from artist as ar left join
album as al
on ar.artistid = al.artistid
group by ar.artistid, ar.aname;

Oracle sql - referencing tables

My school task was to get names from my movie database actors which play in movies with highest ratings
I made it this way and it works :
select name,surname
from actor
where ACTORID in(
select actorid
from actor_movie
where MOVIEID in (
select movieid
from movie
where RATINGID in (
select ratingid
from rating
where PERCENT_CSFD = (
select max(percent_csfd)
from rating
)
)
)
);
the output is :
Gary Oldman
Sigourney Weaver
...but I'd like to also add to this select mentioned movie and its rating. It accessible in inner selects but I don't know how to join it with outer select in which i can work just with rows found in Actor Table.
Thank you for your answers.
You just need to join the tables properly. Afterwards you can simply add the columns you´d like to select. The final select could be looking like this.
select ac.name, ac.surname, -- go on selecting from the different tables
from actor ac
inner join actor_movie amo
on amo.actorid = ac.actorid
inner join movie mo
on amo.movieid = mo.movieid
inner join rating ra
on ra.ratingid = mo.ratingid
where ra.PERCENT_CSFD =
(select max(percent_csfd)
from rating)
A way to get your result with a slightly different method could be something like:
select *
from
(
select name, surname, percent_csfd, row_number() over ( order by percent_csfd desc) as rank
from actor
inner join actor_movie
using (actorId)
inner join movie
using (movieId)
inner join rating
using(ratingId)
(
where rank = 1
This uses row_number to evaluate the "rank" of the movie(s) and then filter for the movie(s) with the highest rating.

How to INTERSECT datas from tables on postgres

I'm having some problems with INTERSECT command. Hope someone could help me.
I want to get the the movieid that appears in the first and second SELECT. After that I want to use these data (that could be in a LIMIT of 10) to receive the titles of the movie in another table.
Something like this, but I'm not doing right:
SELECT movieid
FROM ratings
WHERE votes > 0
INTERSECT SELECT movieid FROM genres WHERE genre = '$_SESSION[genero]'
In this case I should get the movied that appear both on ratings and genres tables.
After this, I want to get these movieids and search the table movies for the movieid and finally show the title. Thank you!
As far as I understood the question, I think this is what you are looking for
select title , movieid from movies
where movieid in
(
SELECT movieid FROM ratings
WHERE votes > 0
INTERSECT
SELECT movieid FROM genres
WHERE genre = '$_SESSION[genero]'
)
select distinct m.title , movieid
from
movies m
inner join
ratings r using (movieid)
inner join
genres g using (movieid)
where r.votes > 0 and g.genre = '$_SESSION[genero]'

sql get a unique ID then count the number of tuples relating to that ID

Database Structure
MovieInfo (mvID, title, rating, year, length, studio)
DirectorInfo(directorID, firstname, lastname)
MemberInfo(username, email, password)
ActorInfo(actorID, firstname, lastname, gender, birthplace)
CastInfo(mvID*, actorID*)
DirectInfo(mvID*, directorID*)
GenreInfo(mvID*, genre)
RankingInfo(username*, mvID*, score, voteDate)
Query
I need to get the director with the largest number of comedy movies. (I'm also required to use the ALL operator). My understanding is getting the list of mvid where genre = 'Comedy" and directorid:
select mvid
from genreinfo
where genre = 'Comedy'
union all
select directorid
from directorinfo
;
But then how do I count the number of movies a specific director has? And how do I get that single one with the highest count of "comedy" movies?
You're on the right track. I'd recommend looking at JOINs.
I've provided a step-by-step answer on how to obtain the desired results. If you just want the final query, go down to step 5 and pick the one appropriate for your DBMS.
1: Selecting all comedy movie IDs:
SELECT mvid
FROM GenreInfo
WHERE genre = 'Comedy';
2: Selecting the directorIDs of those movies
SELECT directorID
FROM DirectInfo
JOIN GenreInfo
ON DirectInfo.mvID = GenreInfo.mvID
WHERE genre = 'Comedy';
3: Selecting the director name of those directors.
SELECT firstname
FROM DirectorInfo
JOIN DirectInfo
ON DirectorInfo.directorID = DirectInfo.directorID
JOIN GenreInfo
ON DirectInfo.mvID = GenreInfo.mvID
WHERE genre = 'Comedy';
4: Grouping that query by director to get number of movies:
SELECT firstname, COUNT(*) AS NumberOfMovies
FROM DirectorInfo
JOIN DirectInfo
ON DirectorInfo.directorID = DirectInfo.directorID
JOIN GenreInfo
ON DirectInfo.mvID = GenreInfo.mvID
WHERE genre = 'Comedy'
GROUP BY DirectorInfo.directorID;
5: Sort the results and get only the first one:
SELECT firstname, COUNT(*) AS NumberOfMovies
FROM DirectorInfo
JOIN DirectInfo
ON DirectorInfo.directorID = DirectInfo.directorID
JOIN GenreInfo
ON DirectInfo.mvID = GenreInfo.mvID
WHERE genre = 'Comedy'
GROUP BY DirectorInfo.directorID
ORDER BY NumberOfMovies
LIMIT 1;
If you're using SQL server, use TOP instead:
SELECT TOP 1 firstname, COUNT(*) AS NumberOfMovies
FROM DirectorInfo
JOIN DirectInfo
ON DirectorInfo.directorID = DirectInfo.directorID
JOIN GenreInfo
ON DirectInfo.mvID = GenreInfo.mvID
WHERE genre = 'Comedy'
GROUP BY DirectorInfo.directorID
ORDER BY NumberOfMovies;
You can use a join and group by to get the result.
select DirectorID,COUNT(mvid)
from DirectInfo d
inner join genreinfo g
ON d.mvid=g.mvid
where genre ='Comedy'
GROUP BY DirectorID
ORDER BY COUNT(mvid)
This is homework? Well, right now you are selecting a list of IDs, some of them representing directors, others representing movies. You notice that this is not at all what you are supposed to do, right?
What you want is a list of directors. So you select from the DirectorInfo table. You also want information about his movies (excatly: the number of movies of a certain kind). So you must join that information from MovieInfo. Now think about what else you need to glue together to get from director to their movies. Then think about how to glue in that genre criterium.
Once you have joined it all together, then you group your results. You want one record per director (instead of ane record per director and movie), so you make a group and count within that group.
I hope this helps you solve your task. Good luck!
select di.directorid, count(1) as 'no_of_comedy_movies'
from DirectorInfo di inner join join DirectInfo dri
on di.directorid = dri.directorid
inner join genreinfo gi
on gi.mvid = dri.mvid
where gi.genre = 'Comedy'
group by dri.directorID
order by no_of_comedy_movies

SQL select on a many-to-many table

I've got 3 tables: Movies, Actors, and MovieActors. MovieActors is a many-to-many relationship of Movies and Actors with columns MovieActorId, MovieId, and ActorId
How do I find movies that have a certain set of actors in it? For example I want to find all movies that have both Michael Fassbender (actor Id 1) and Brad Pitt (actor Id 2) in them. What would the query look like?
One way is to join the tables. Filter for the actors and then insure the count has the number of actors you want in it (2 in this case)
SELECT
m.MovieID
FROM
Movies m
INNER JOIN MovieActors ma
ON m.MovieID = ma.MovieID
WHERE
ma.ActorID IN (1,2)
GROUP BY
m.MovieID
HAVING COUNT(DISTINCT ma.ActorID) = 2
DEMO
Note
Thanks to user814064 for pointing out that since Actors can have more than one role on a movie we need to count the DISTINCT ma.ActorID not just * The SQL Fiddle Demo demonstrates the difference
select m.movieid
from movies m
inner join movieactors ma on ma.movieid = m.movieid
where ma.actorid in (1,2)
group by m.movieid
having count(distinct ma.actorid) = 2
To keep it simple, you can just do two in clauses:
select * from Movies m
where m.MovieId in (select MovieId from MovieActors where ActorId = 1)
and m.MovieId in (select MovieId from MovieActors where ActorId = 2)
Performance may not be as good as a single join, but it's clean and easy to read.