SQL Sub Query to find data - sql

I need to find all titles of films that have greater replacement cost than some R rating film.
film table has...
film_id, title, description, release_year, language_id, original_language_id, rental_duration, rental_rate, length, replacement_cost, rating, special_features, last_update
This is not working...
SELECT
title
FROM film
WHERE replacement_cost > (SELECT
replacement_cost
FROM film
WHERE rating = 'R');

SELECT title
FROM film
WHERE replacement_cost >
(SELECT MAX(replacement_cost)
FROM film
WHERE rating = 'R');
You need aggregation of some kind to make the subselect a scalar value. Using MAX() in your query would give you the highest replacement_cost of a rated R film.
To get the replacement_cost of a particular film then you need to do the same thing but modify your where statement to be the film you want. say you know the film_id then you could do:
SELECT title
FROM film
WHERE replacement_cost >
(SELECT replacement_cost
FROM film
WHERE film_id = 123);
Note I took off MAX() because when using the primary key or criteria that will choose a single scalar value (1 column 1 row) from the table you don't actually have to use aggregation.

Related

Why do we use a sub-queries in SQL?

I don't understand we would need to use sub-queries in data analysis or why the order matters. Can someone explain it to me?
Example: Why do the 2 codes below produce 2 different outputs from the difference in ordering of the code? They are both trying to find the lowest rental price in 2006.
Code 1:
SELECT
film_id
,title
FROM Films
WHERE release_year = 2006
AND rental_rate =
(
SELECT
MIN(rental_rate)
FROM Films
);
Code 2:
SELECT
film_id
,title
FROM Films
WHERE rental_rate =
(
SELECT
MIN(rental_rate)
FROM Films
WHERE release_year = 2006
);

find an average of a column using group with inner join and then filtering through the groups

I've been trying to solve an sqlite question where I have two tables: Movies and movie_cast.
Movies has the columns: id, movie_title, and `score. Here is a sample of the data:
11|Star Wars|76.496
62|2001:Space Odyssey|39.064
152|Start Trek|26.551
movie_cast has the columns: movie_id, cast_id, cast_name, birthday, popularity. Here is a sample.
11|2|Mark Hamill|9/25/51|15.015
11|3|Harrison Ford|10/21/56|8.905
11|5|Peter Cushing|05/26/13|6.35
IN this case movies.id and movie_cast.movie_id are the same.
The question is to Find the top ten cast members who have the highest average movie scores.
Do not include movies with score <25 in the average score calculation.
▪ Exclude cast members who have appeared in two or fewer movies.
My query is as below but it doesn't seem to get me the right answer.
SELECT movie_cast.cast_id,
movie_cast.cast_name,
printf("%.2f",CAST(AVG(movies.score) as float)),
COUNT(movie_cast.cast_name)
FROM movies
INNER JOIN movie_cast ON movies.id = movie_cast.movie_id
WHERE movies.score >= 25
GROUP BY movie_cast.cast_id
HAVING COUNT(movie_cast.cast_name) > 2
ORDER BY AVG(movies.score ) DESC, movie_cast.cast_name ASC
LIMIT 10
The answers I get are in the format cast_id,cat_name,avg score.
-And example is: 3 Harrison Ford 52.30
I've analyzed and re-analyzed my logic but to no avail. I'm not sure where I'm going wrong. Any help would be great!
Thank you!
This is how I would write the query:
SELECT mc.cast_id,
mc.cast_name,
PRINTF('%.2f', AVG(m.score)) avg_score
FROM movie_cast mc INNER JOIN movies m
ON m.id = mc.movie_id
WHERE m.score >= 25
GROUP BY mc.cast_id, mc.cast_name
HAVING COUNT(*) > 2
ORDER BY AVG(m.score) DESC, mc.cast_name ASC
LIMIT 10;
I use aliases for the tables to shorten the code and make it more readable.
There is no need to cast the average to a float because the average in SQLite is always a real number.
Both COUNT(movie_cast.cast_name) can be simplified to COUNT(*) but the 1st one in the SELECT list is not needed by your requirement (if it is then add it).
The function PRINTF() returns a string, but if you want a number returned then use ROUND():
ROUND(AVG(m.score), 2) avg_score

concatenate some of the column content and select in one query

Here is my film table:
FILM (Catalog_Num, Format, Title, Year, Number_Discs, Rating, Timing, Genre)
I want to concatenate the genre column if the year is before 1970 and it cannot be repeated,
for example, if the genre is Romantic --> (Classic Romance) its ok.
but is the genre is already Classic, it should remain Classic rather than (Classic Classic)
after that, I have to list id, title, and genre of all classic film.
Here is what I tried:
select genre|| 'Classic'
from film where (year <1970 and genre not in ('Classic'));
select film_id, title, genre
from inventory, film
where film.catalog_num = inventory.catalog_num and genre like '%Classic%';
But the output only shows all the genre in classic type, instead of romance classics.
Further, I have to finish in one query, but I don't know how to combine them.
Use a subquery to manipulate the data and feed that into your main query:
with films as (
select catalog_num
, title
, case
when (year <1970 and genre not in ('Classic'))
then 'Classic ' || genre
else genre end as genre
from film
)
select inventory.film_id
, films.title
, films.genre
from inventory
join films on films.catalog_num = inventory.catalog_num
where films.genre like '%Classic%';
Your question says you want Romantic --> (Classic Romance) but your posted code has genre||'Classic which is the other way round. I have changed the code to generate 'Classic Romance'.
Note: you haven't aliased the columns in the second query's projection, so I had to guess which columns come from film and which from inventory. You will need to correct any wrong guess.
If you only want your definition of class films, then you don't have to munge the genre. Just do:
select i.film_id, f.title, f.genre
from inventory i join
films f
on f.catalog_num = i.catalog_num
where f.genre like '%Classic%' or f.year < 1970;
If you still want "Classic" in the genre:
select i.film_id, f.title,
(case when f.year < 1970 and f.genre <> 'Classic'
then 'Classic ' || f.genre
else f.genre
end) as genre
from inventory i join
films f
on f.catalog_num = i.catalog_num
where f.genre like '%Classic%' or f.year < 1970;
Your question is a little vague on whether "Classic" can be part of a genre name rather than the entire name. So you might want:
(case when f.year < 1970 and f.genre not like '%Classic%'
then 'Classic ' || f.genre
else f.genre
end) as genre
Note that such comparisons are usually case-sensitive in Oracle, so you might need to take uppercase/lowercase into account as well.

How to make SQL request to get table of counted keywords?

I need to make a single SQL request like this:
SELECT genres, count(*) FROM books WHERE genres LIKE 'Fiction%'
But I need to use many keyword like 'Nonfiction%', 'Historical' ect. The output should be a table:
Fiction 8654
Nonfiction 6543
Historical 2344
What SQL request I have to use to get this result?
Hopefully, you can just do:
SELECT genres, count(*)
FROM books
GROUP BY genres;
In fact, you could just do this if you had a table called BookGenres with one row per book and per genre. That is the right way to store this data.
In this case you appear to be looking only for the first genre in the list. You can use case:
select (case when genres like 'Fiction%' then 'Fiction'
when genres like 'Nonfiction%' then 'Nonfiction'
when genres like 'Historical%' then 'Historical'
else 'Other'
end) as genre, count(*)
from books
group by (case when genres like 'Fiction%' then 'Fiction'
when genres like 'Nonfiction%' then 'Nonfiction'
when genres like 'Historical%' then 'Historical'
else 'Other'
end);
You could use group by
SELECT genres, count(*)
FROM books
GROUP BY genres
or for a set of values
SELECT genres, count(*)
FROM books
WHERE genres in ( 'Fiction', 'Nonfiction', 'Historical')
GROUP BY genres

SQL instead of trigger and update

I have these tables:
Movie ( mID, title, year, director )
Reviewer ( rID, name )
Rating ( rID, mID, stars, ratingDate )
and some views:
View LateRating contains movie ratings after January 20, 2011. The view contains the movie ID, movie title, number of stars, and rating date.
create view LateRating as
select distinct R.mID, title, stars, ratingDate
from Rating R, Movie M
where R.mID = M.mID
and ratingDate > '2011-01-20'
View HighlyRated contains movies with at least one rating above 3 stars. The view contains the movie ID and movie title.
create view HighlyRated as
select mID, title
from Movie
where mID in (select mID from Rating where stars > 3)
View NoRating contains movies with no ratings in the database. The view contains the movie ID and movie title.
create view NoRating as
select mID, title
from Movie
where mID not in (select mID from Rating)
Here's my data set : https://prod-c2g.s3.amazonaws.com/db/Winter2013/files/viewmovie.sql
I'm asked to write an instead-of trigger that enables updates to the stars attribute of view LateRating.Here's my approach.
CREATE trigger update_LateRating_title INSTEAD OF
UPDATE OF stars ON LateRating
BEGIN
UPDATE Rating SET stars = stars - 2
WHERE Rating.mID = old.mID
AND Rating.mID IN (SELECT stars FROM Rating WHERE stars > 2);
END;
It gives almost right answer but there is only one wrong row that being 201 101 4 2011-01-27.4 should be 2.What is wrong?
Let's look at your WHERE condition:
...AND Rating.mID IN (SELECT stars FROM Rating WHERE stars > 2)
It states that Rating.mID should be among values of stars selected from rating. I'm unsure what was intended here. Let's assume you want to decrease the number of stars by 2 for any attempt to modify stars (that's weird: you're turning queries which would increase stars into some unrelated queries) if the original number of stars in that line of Rating table was greater than 2.
CREATE trigger update_LateRating_stars INSTEAD OF
UPDATE OF stars ON LateRating
BEGIN
UPDATE Rating SET stars = stars - 2
WHERE Rating.mID = old.mID
AND Rating.stars > 2;
END;
Again, there are many possible interpretations of what you want, and
they all are more or less weird: mID field in LateRating is not
enough to identify the original record in Rating (maybe rID and
mID together would be enough, if you don't allow the same reviewer to
hold different opinions on the same movie simultaneously).