I'm having some trouble trying to make a view with a calculated average column, for every movie row I need an average rating based on all the ratings for this movie in the rating table.
Movie table:
CREATE TABLE IF NOT EXISTS streaming_db.movie(
id BIGINT NOT NULL auto_increment
,name VARCHAR (100)
,description VARCHAR (1000)
,PRIMARY KEY (id)
) engine = InnoDB;
Rating table:
CREATE TABLE IF NOT EXISTS streaming_db.rating(
id BIGINT NOT NULL auto_increment
,rating_score DECIMAL(4, 2) NOT NULL
,comment VARCHAR (255) NULL
,id_profile BIGINT NOT NULL
,id_movie BIGINT NOT NULL
,PRIMARY KEY (id)
) engine = InnoDB;
Here's what I have so far:
CREATE VIEW streaming_db.midia
AS
SELECT name,
description
FROM streaming_db.movie a
INNER JOIN (SELECT avg(rating_score) AS averageRating from streaming_db.rating where
rating.id_movie = a.id);
It was telling me that a derived table needs its own alias, and I don't know if that really gives me the average per row.
You are attempting a correlated subquery in the FROM clause. Well, this is actually a real thing, called a lateral join.
But that is not your intention. Move the logic to the SELECT:
SELECT m.name, m.description,
(SELECT avg(rating_score)
FROM sistema_streaming_db.rating r
WHERE r.id_movie = m.id
) as averageRating
FROM streaming_db.movie m;
Note that I fixed the table aliases so they are abbreviations for the table names, which makes the query much easier to read.
Related
I am fairly new with databases and I am starting with mysql.
I have 4 tables (movie, genre, movieGenre and movieRating):
movie:
CREATE TABLE `movie` (
`movieId` INT NOT NULL,
`title` VARCHAR(155) NOT NULL,
PRIMARY KEY (`movieId`)
);
genre
CREATE TABLE `genre` (
`code` INT NOT NULL AUTO_INCREMENT,
`genre` VARCHAR(20) NOT NULL,
PRIMARY KEY (`code`)
);
movieGenre
CREATE TABLE `movieGenre` (
`movieId` INT,
`genreId` INT,
CONSTRAINT `fk_movieGenre_movie` FOREIGN KEY (`movieId`) REFERENCES `movie`(`movieId`),
CONSTRAINT `fk_movieGenre_genre` FOREIGN KEY (`genreId`) references `genre`(`code`)
);
and movieRating
CREATE TABLE `movieRating` (
`userId` INT NOT NULL AUTO_INCREMENT,
`movieId` INT NOT NULL,
`rating` FLOAT NOT NULL,
`date` DATE,
CONSTRAINT `fk_movieRating_user` FOREIGN KEY (`userId`) REFERENCES `user`(`userId`),
CONSTRAINT `fk_movieRating_movie` FOREIGN KEY (`movieId`) REFERENCES `movie`(`movieId`)
);
I need to find the average rate for each movie genre, sorted in descended average rating value and if a genre does not have any associated rating, it should be reported with 0 ratings value
I am lost. I don't know how to achieve this result. Could you please help me?
I have figured out how to find the avg rate for each movie but I don't know how to change this so I find for each genre:
SELECT `movie`.`movieId`, AVG(`movieRating`.`rating`) FROM `movie`
INNER JOIN `movieRating` ON `movie`.`movieId` = `movieRating`.`movieId`
GROUP BY `movieRating`.`movieId`
ORDER BY AVG(`movieRating`.`rating`) DESC;
Well, I have put an ID in your genre table, otherwise I can't make this work. So, it has become:
CREATE TABLE `genre` (
`genreId` INT,
`code` INT NOT NULL AUTO_INCREMENT,
`genre` VARCHAR(20) NOT NULL,
PRIMARY KEY (`code`)
);
And I have to make the assumption that all the genres are defined in this table. I'll take this table as a base, and, as you suggested use a subquery:
SELECT
genre.code,
genre.genre,
(<my sub select comes here>)
FROM
genre;
This basically gets you a list of all genres. Now it is up to the subquery to give the average rate for the movies in each genre. That subquery could look something like this:
SELECT AVG(movieRating.rating)
FROM movieRating
JOIN movie ON movie.movieId = movieRating.movieId
JOIN movieGenre ON movieGenre.movieId = M.movieId
WHERE movieGenre.genreId = genre.genreId;
I kept it very simple. We start with the average we want, from movieRating, and work through the movie and movieGenre tables to get to the genreId in that last table. Notice the genre.genreId which comes from the main query. We are implicitly grouping by genreId.
Now you can put this subselect in the main query, but that still doesn't solve the situation in which there is not rating to take an average from. It would result in NULL, meaning: no result. That is almost good enough, but you could put a IFNULL() around it to get a proper zero result.
The total query would then become this:
SELECT
genre.code,
genre.genre,
IFNULL((SELECT AVG(movieRating.rating)
FROM movieRating
JOIN movie ON movie.movieId = movieRating.movieId
JOIN movieGenre ON movieGenre.movieId = M.movieId
WHERE movieGenre.genreId = genre.genreId), 0) AS Average
FROM
genre;
I can't guarantee this will work since I cannot test it, and testing is everything when writing queries.
You should left join all tables and then group by gerne
SELECT `genre`,AVG(IFNULL(`rating`,0)) avgrate
FROM `movie` m
LEFT JOIN `movieRating` mr ON m.`movieId` = mr.`movieId`
LEFT JOIN movieGenre mg ON mg.`movieId` = m.`movieId`
LEFT JOIN `genre` g ON g.`code` = mg.`genreId`
GROUP BY `genre`
I general produce data for your tables, and then start by joing the tables, and see f you get the result you want, if not change the joins to LET Join one by one till you get the result you want, of course you need ro calculate teh avg from 3 or 4 movies
I have a table that looks like this:
id int primary key
uniqueID string --not uniquely indexed
foreignKeyID int --foreignKey to another table
I want to find all the uniqueIds in this table that exist for foreign key 1 that do not exist for foreign key 2
I thought I could do something like this:
SELECT * FROM table t1
LEFT JOIN table t2
ON t1.uniqueID = t2.uniqueID
WHERE
t1.foreignKeyID = 1
AND t2.uniqueID IS NULL
However this is never giving me results. I can make it work with a NOT IN subquery but this is a very large table so I suspect a solution using joins will be faster.
Looking for the best way to structure this query.
Here's an sample data set and SQL Fiddle with an example of the working NOT IN query I am trying to convert to a LEFT JOIN:
CREATE TABLE `table` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uniqueID` varchar(255),
`foreignKeyID` int(5) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `table` (uniqueID, foreignKeyID) VALUES ('aaa', 1), ('bbb', 1);
http://sqlfiddle.com/#!9/48a3f3/4 and a non-working LEFT JOIN I thought would be equivalent.
Thanks!
Try this, seems to be working if understood the question properly:
SELECT *
FROM `table` t
LEFT JOIN `table` tt ON tt.uniqueID = t.uniqueID AND tt.foreignKeyID <> 1
WHERE t.foreignKeyID = 1 AND tt.id IS NULL;
I wrote an example to describe my problem in a more complex database.
I use MySQL 5.7 with Delphi XE8.
I have 2 tables:
CREATE TABLE customers
(ID INT NOT NULL AUTO_INCREMENT ,
Name VARCHAR(30) NOT NULL ,
PRIMARY KEY (ID)
) ENGINE = InnoDB;
CREATE TABLE orders
(IDorders INT NOT NULL AUTO_INCREMENT ,
customerID INT NOT NULL ,
Description VARCHAR(30) NOT NULL ,
DateOrder Date NOT NULL ,
PRIMARY KEY (IDorders),
INDEX DateOrderIndex (DateOrder, customerID) USING BTREE;
) ENGINE = InnoDB;
select *,
(SELECT MAX(DateOrder) FROM Orders WHERE Orders.customerID =
customers.ID) as LastOrder
FROM customers
My problem is:
the customer table has 58,000 records
and
the orders table has 200,000 records
The query result occurs after 28 seconds.
Where am I doing wrong?
You can try to use JOIN with MAX and GROUP BY in subquery.
select c.*,
t1.maxDt
FROM customers c
JOIN (
SELECT customerID,MAX(DateOrder) maxDt
FROM Orders
GROUP BY customerID
) t1 on t1.customerID = c.ID
Note
if your query is slow you can try to create indexs on Orders.customerID and
customers.ID
I need to obtain records in a key-value table with the following structure:
CREATE TABLE `PROPERTY` (
`id` int(11) NOT NULL,
`key` varchar(64) NOT NULL,
`value` text NOT NULL,
PRIMARY KEY (`id`,`key`)
);
I need to get all ids that have MULTIPLE specific key-value entries. For example, all ids that have keys "foo", "bar", and "foobar".
Simply use GROUP BY to group and then check the group count to count multiple values:
Select
id
from
`PROPERTY`
group by
key, value
having
count(*) > 1
Given updated question...
since you know the specific keys, you also know how many there are... so a count distinct in having should do it... along with a where...
SELECT id
FROM `PROPERTY`
Where key in ('foo','bar','foobar')
GROUP BY ID
having count(distinct key) = 3
I f you need the id of all the rows for key,value count(= >1)
select id from `PROPERTY`
where (key, value) in (select key, value from `PROPERTY`group by
key, value
having
count(*) > 1)
I modeled a small database for easier explanation:
CREATE TABLE bands (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(120) NULL,
PRIMARY KEY(id)
)
TYPE=InnoDB;
CREATE TABLE albums (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
band_id INTEGER UNSIGNED NOT NULL,
album_name VARCHAR(120) NULL,
rating INTEGER UNSIGNED NULL,
insertion_date TIMESTAMP NULL,
PRIMARY KEY(id),
INDEX albums_FKIndex1(band_id),
FOREIGN KEY(band_id)
REFERENCES bands(id)
ON DELETE NO ACTION
ON UPDATE NO ACTION
)
TYPE=InnoDB;
Now, pretending that we already have some bands and many albums registered in their respective tables, I want to select ONLY the last inserted album from each registered band.
PS: I have to use the "album.insertion_date" field to determine which album is the last inserted.
Try joining the two tables and filtering by insertion_date and band:
SELECT al.*
FROM albums al
INNER JOIN bands b ON al.band_id=b.id
WHERE al.insertion_date=(
SELECT max(insertion_date)
FROM albums
WHERE band_id=b.id
)
Try this one:
select b.name, a.album_name, a.isertion_date
from bands b, albums a
where a.band_id = b.id
and a.insertion_date = (select max(a1.insertion_date) from albums a1 where a1.band_id = b.id)
Considering that you have the albums' ids to be AUTO_INCREMENT and the possibility for the insertion_date to be NULL(as it is the default value), using insertion_date to determine the results is not the smartest thing to do but ... there you go:
SELECT DISTINCT band, last_album, insertion_date
FROM (
SELECT bands.name AS band, albums.album_name AS last_album, albums.insertion_date
FROM bands
JOIN albums ON bands.id=albums.band_id
ORDER BY albums.insertion_date DESC
) t1
GROUP BY band;