There is a query in SQL where I am badly stuck, I have tried every possible way I could but couldn’t get the solution. I have 4 tables named: user, item, buys, rates.
CREATE TABLE User (
id integer,
name varchar(30),
Primary Key (id)
)
INSERT INTO User(id, name)
VALUES
('1', 'Lorren'),
('2', 'Smith'),
('3', 'Stephen'),
('4', 'David'),
('5', 'Sophie'),
('6', 'Alex'),
('7', 'Henry'),
('8', 'Jasmine'),
('9', 'Anderson'),
('10', 'Bilal')
CREATE TABLE Item (
id integer,
description varchar(50),
category varchar(30),
price integer,
Primary Key (id)
)
INSERT INTO Item(id, description, category, price)
VALUES
('50', 'Princess Diary', 'Book', '8'),
('51', 'Frozen', 'Book', '4'),
('52', 'Tangled', 'Book', '3'),
('53', 'Oak Table', 'Furniture', '370'),
('54', 'Doble Bed', 'Furniture', '450'),
('55', 'Metal Cupboard', 'Furniture', '700'),
('56', 'Levi 501', 'Clothes', '90'),
('57', 'Corduroy Coat', 'Clothes', '230'),
('58', 'Straight Trousers', 'Clothes', '45'),
('59', 'Black Sequin Top', 'Clothes', '85')
CREATE TABLE Buys (
user integer,
item integer,
price integer,
Primary Key (user, item),
Foreign key (user) REFERENCES User(id),
Foreign Key (item) REFERENCES Item(id)
)
INSERT INTO Buys
VALUES ('1', '52', '3'),
('1', '56', '90'),
('2','56','100'),
('2', '54', '450'),
('5', '53', '400'),
('5', '55', '700'),
('5', '59', '90'),
('6', '57', '230'),
('10', '58', '50'),
('8', '50', '8')
CREATE TABLE Rates (
user integer,
item integer,
rating integer CHECK (0<=rating<=5),
Primary Key (user, item),
Foreign key (user) REFERENCES User(id),
Foreign Key (item) REFERENCES Item(id)
)
INSERT INTO Rates
VALUES
('1', '52', '5'),
('1', '56', '3'),
('2', '54', '5'),
('2', '55', '4'),
('2', '56', '2'),
('5', '53', '5'),
('5', '55', '5'),
('8', '50', '1'),
('8', '55', '3'),
('9', '55', '4')
I have to find against each user all the items not bought by him, but display only those item/items among them which has/have the highest average rating. So the result should display only those item/items which were not bought by him and have the highest average rating. Rating is 1-5, and each item may have different ratings so avg rating can be calculated for each, but I am not able to find out items with highest avg rating gaainst each user which are not bought by him. I am working in MYSQL, I am stuck in here for 6 days and even my friends tried nobody could solve it. Can anybody help?
Expected Output considering the current tables should be like this:
User Items With Highest Average
Lorren 53
Lorren 54
Smith 52
Smith 53
Stephen 52
Stephen 53
Stephen 54
David 52
David 53
David 54
Sophie 52
Sophie 54
Alex 52
Alex 53
Alex 54
Henry 52
Henry 53
Henry 54
Jasmine 52
Jasmine 53
Jasmine 54
Anderson 52
Anderson 53
Anderson 54
Bilal 52
Bilal 53
Bilal 54
Ok, definitely not my prettiest work, especially since I don’t usually work in MySQL (EDIT: SQLFiddle is back up. Fixed an inner group, now this works):
Here is the SQLFiddle
In TSQL, I would at least use a CTE for that table of average ratings. This was a lot tougher than it looked initially!
EDIT: Some explanation follows. The first thing to get is each item’s average rating, using 0 for items with no rating (hence the
COALESCE()
statement):This will list each item id once with its average rating. I named this
averages
, and I actually used it query twice (naming itiwa
the second time. I don’t remember what “iwa” was supposed to mean any more…), once to get the actual highest rating:and named that
MaxOuterRating
, thenINNER JOIN
ed that result back ontoiwa
, onAverageRating = MaxRating
, to only get item(s) with that highest rating:This result is contained in the
topItems
alias.Now that we have only the item(s) with the top rating,
CROSS JOIN
withUser
to get a table with every top item for every user:This result is in
topItemsAllUsers
.Finally, do
LEFT JOIN
withBuys
on both user id and item id, then restrict results to only those rows where there are noBuys
records associated (This is usually called an exclude join):Et viola. None of the operations is particularly difficult, but they are nested up so badly, it was difficult to see how to attack. I do not doubt this could be improved greatly, but this does return expected results.
So just for starters, the list of items not bought by respective users is as follows, right?
… in which case …
… should produce the desired result
Edited to incorporate Paul Griffin’s observation, although in doing so, I’ve perhaps made the query more convoluted than it needs to be.