How do I limit the result to just different results? - mysql

The database itself is about storing cocktails with their own recipes (Recipe) and ingredients (RecipeIngredient). Each user (User) has their own "pantry" (UserIngredients) in which they can store the ingredients they have at home. This query should now show them the cocktails they can mix
I've got the following query:
SELECT u.User_Name, r.Recipe_Name
FROM User u
INNER JOIN UserIngredient ui ON u.User_ID = ui.User_ID
INNER JOIN RecipeIngredient ri ON ui.Ingredient_ID = ri.Ingredient_ID
INNER JOIN Ingredient i ON ri.Ingredient_ID = i.Ingredient_ID
INNER JOIN Recipe r ON ri.Recipe_ID = r.Recipe_ID
WHERE u.User_Session = 'DgRkQztkvUhotfSf53l7ciiI8rOhKtuvoPqCTvdlBXWTn9cYxz'
and would like to know if it is possible to just get one "r.Recipe_Name" per recipe and not one for each ingredient.
My tablelayout is the following:
CREATE TABLE User
(
User_ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
User_Pass TEXT NOT NULL,
User_Name TEXT NOT NULL,
User_Surname TEXT NOT NULL,
User_Nickname TEXT,
User_EMail TEXT,
User_Session VARCHAR(50) UNIQUE,
User_Admin BOOLEAN
);
CREATE TABLE Recipe
(
Recipe_ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Recipe_Name TEXT NOT NULL,
Recipe_Clicks INT,
Recipe_Description TEXT
);
CREATE TABLE Ingredient
(
Ingredient_ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Ingredient_Name VARCHAR(255) UNIQUE,
Ingredient_Description TEXT
);
CREATE TABLE RecipeIngredient
(
RecipeIngredient_ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
RecipeIngredient_Amount DECIMAL(8,2) NOT NULL,
MeasuringUnit_ID INT NOT NULL,
Recipe_ID INT NOT NULL,
Ingredient_ID INT NOT NULL,
FOREIGN KEY (MeasuringUnit_ID) REFERENCES MeasuringUnit(MeasuringUnit_ID),
FOREIGN KEY (Recipe_ID) REFERENCES Recipe(Recipe_ID),
FOREIGN KEY (Ingredient_ID) REFERENCES Ingredient(Ingredient_ID)
);
CREATE TABLE UserIngredient
(
UserIngredient_ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Ingredient_ID INT NOT NULL,
User_ID INT NOT NULL,
FOREIGN KEY(Ingredient_ID) REFERENCES Ingredient(Ingredient_ID),
FOREIGN KEY(User_ID) REFERENCES User(User_ID)
);

Try
SELECT u.User_Name, MAX(r.Recipe_Name)
FROM User u
INNER JOIN UserIngredient ui ON u.User_ID = ui.User_ID
INNER JOIN RecipeIngredient ri ON ui.Ingredient_ID = ri.Ingredient_ID
INNER JOIN Ingredient i ON ri.Ingredient_ID = i.Ingredient_ID
INNER JOIN Recipe r ON ri.Recipe_ID = r.Recipe_ID
WHERE u.User_Session = 'DgRkQztkvUhotfSf53l7ciiI8rOhKtuvoPqCTvdlBXWTn9cYxz'
GROUP BY u.User_Name, r.Recipe_Name
Not sure about this but it sounds like multiple ingredients will have the same recipe so just select max, which will return the only recipe name and if you group by user name + recipe name it might give you what you need.

To get the desired result using this database, try
SELECT DISTINCT u.User_Name, r.Recipe_Name
FROM User u
INNER JOIN UserIngredient ui ON u.User_ID = ui.User_ID
INNER JOIN RecipeIngredient ri ON ui.Ingredient_ID = ri.Ingredient_ID
INNER JOIN Ingredient i ON ri.Ingredient_ID = i.Ingredient_ID
INNER JOIN Recipe r ON ri.Recipe_ID = r.Recipe_ID
WHERE u.User_Session = 'DgRkQztkvUhotfSf53l7ciiI8rOhKtuvoPqCTvdlBXWTn9cYxz'
My guess is that users create Recipies, why don't you instead add User_ID to Receipe?

you can just use distinct
SELECT DISTINCT u.User_Name, r.Recipe_Name
FROM User u
INNER JOIN UserIngredient ui ON u.User_ID = ui.User_ID
INNER JOIN RecipeIngredient ri ON ui.Ingredient_ID = ri.Ingredient_ID
INNER JOIN Ingredient i ON ri.Ingredient_ID = i.Ingredient_ID
INNER JOIN Recipe r ON ri.Recipe_ID = r.Recipe_ID
WHERE u.User_Session = 'DgRkQztkvUhotfSf53l7ciiI8rOhKtuvoPqCTvdlBXWTn9cYxz'

Related

Mysql find the book with the highest rating for each country. If there is a tie "print" the book with the highest number of ratings

I have the following tables:
CREATE TABLE `country` (
`name` VARCHAR(60) NOT NULL,
`code` VARCHAR(3) UNIQUE NOT NULL,
PRIMARY KEY (`code`)
);
CREATE TABLE `user` (
`userId` INT UNIQUE NOT NULL AUTO_INCREMENT,
`country` VARCHAR(3) NOT NULL,
`age` INT NOT NULL,
PRIMARY KEY (`userId`),
CONSTRAINT `fk_user_country` FOREIGN KEY (`country`) REFERENCES `country`(`code`)
);
CREATE TABLE `bookRating` (
`userId` INT NOT NULL,
`isbn` VARCHAR(13) NOT NULL,
`rate` INT NOT NULL,
`date` DATE NOT NULL,
CONSTRAINT `fk_bookRating_user` FOREIGN KEY (`userId`) REFERENCES `user`(`userId`),
CONSTRAINT `fk_bookRating_book` FOREIGN KEY (`isbn`) REFERENCES `book`(`isbn`)
);
CREATE TABLE `book` (
`isbn` varchar(13) UNIQUE NOT NULL,
`bookTitle` VARCHAR(280),
`bookAuthor` VARCHAR(150),
`yearPublication` int(4),
-- `yearPublication` must be an integer because we have value less that 1901 in dataset
`publisher` VARCHAR(135),
PRIMARY KEY (`isbn`),
CONSTRAINT `publication_yea_chk` check ((`yearPublication` > -1) && (`yearPublication` < 2101))
);
As I am saying on the title I want to find the book with the highest average rating, For each country
I have tried this query:
select T1.name, T1.BookTitle, Rate
from
(
select C.Code, AVG(BR.rate) MAXRating
from `bookRating` BR
inner join `book` B on BR.isbn = B.isbn
INNER JOIN `USER` U ON BR.UserID = U.USERId
INNER JOIN `COUNTRY` C ON U.country = C.Code
group by C.Code
) T
inner join
(
select C.Code, C.name, B.BookTitle, BR.ISBN, BR.rate
from `bookRating` BR
inner join `book` B on BR.isbn = B.isbn
INNER JOIN `USER` U ON BR.UserID = U.USERId
INNER JOIN `COUNTRY` C ON U.country = C.Code
) T1 ON T.Code = T1.Code AND T.MAXRATING = T1.RATE;
I am pretty sure this works. But I want to make it like If 2 or more books have the same average rating I want the one with the highest number of ratings.
I figured that I could use an If() statement, but how could I If(... , a condition)
How could I do it?
UPDATE
I have made the database and inserted some info in db fidle:
https://www.db-fiddle.com/f/s6wKhKhxXMX1W2x9VZn9da/1
You can join the tables, aggregate by country and book to get all average ratings and use window functions MAX() and FIRST_VALUE() on the results of the aggregation to get the book with the highest average for each country:
SELECT DISTINCT c.name,
FIRST_VALUE(b.bookTitle) OVER (
PARTITION BY c.Code
ORDER BY AVG(r.rate) DESC, COUNT(*) DESC
) bookTitle,
MAX(AVG(r.rate)) OVER () AverageRating
FROM country c
INNER JOIN users u ON u.country = c.Code
INNER JOIN bookRating r ON r.UserID = u.UserID
INNER JOIN book b ON b.isbn = r.isbn
GROUP BY c.Code, b.isbn;
See the demo.

Connecting two tables using third table with foreign key in mySQL

I have three tables.
CREATE TABLE IF NOT EXISTS users (
userID int AUTO_INCREMENT,
username text NOT NULL,
password text NOT NULL,
PRIMARY KEY (userID)
);
CREATE TABLE IF NOT EXISTS schedule (
actID int AUTO_INCREMENT,
actName text NOT NULL,
actDay text NOT NULL,
actStart text NOT NULL,
actStop text NOT NULL,
PRIMARY KEY (actID)
);
CREATE TABLE IF NOT EXISTS connection (
userID int,
actID int,
FOREIGN KEY (userID) REFERENCES users(userID),
FOREIGN KEY (actID) REFERENCES schedule(actID)
);
I am using a third table (connection) to connect the first two tables (users and schedule) with FOREIGN KEY. How can I get all activities from schedule for a specific user?
If you want to search by user's name you must join all 3 tables:
select u.*, s.*
from users u
inner join connection c on c.userID = u.userID
inner join schedule s on s.actID = c.actID
where u.username = ?
If you want to search by user's id you must join only 2 tables:
select c.userID, s.*
from connection c inner join schedule s
on s.actID = c.actID
where c.userID = ?
If a user does not have any activities but you want in the results 1 row, with no activity then use left joins:
select u.*, s.*
from users u
left join connection c on c.userID = u.userID
left join schedule s on s.actID = c.actID
where u.username = ?

How to select a user who has read Steven King and has not read William Shakespeare

While making my study mysql project, I failed to apply JOIN's knowledge in an empirical situation, have issues with the logic of the query itself.
My aim is to make a query that requires 6 tables to interact.
First connected table batch:
CREATE TABLE `books` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`title` VARCHAR(100) NOT NULL,
`condition` ENUM('mint', 'new', 'medium', 'poor', 'needs replacement'),
`date_added` DATE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `authors` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(50) NOT NULL,
`pseudonim` VARCHAR(50) NOT NULL,
`year_of_birth` INT(4) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
connected many-to-many via pivoting table
CREATE TABLE `authors_books` (
`author_id` INT(11) UNSIGNED NOT NULL,
`book_id` INT(11) UNSIGNED NOT NULL,
PRIMARY KEY (`author_id`, `book_id`),
CONSTRAINT `fk1_authors_authors_id` FOREIGN KEY (`author_id`) REFERENCES `authors` (`id`)
ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT `fk2_books_book_id` FOREIGN KEY (`book_id`) REFERENCES `books` (`id`)
ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
The second batch is as it's seen on the screenshot - books, users and user_orders:
https://paste.pics/30d57ef8c6f5adfab23ce1158fb30f09
What I am trying is to get all users who have read King and haven't read Shakespeare.
I'm looking at it in the following way:
addressing users table since I need a user: SELECT * FROM users
i need to search via books, so
LEFT JOIN user_orders ON users.id = user_orders.user_id
LEFT JOIN books ON user_orders.book_id = books.id
Following this logic, I ended up chaining dysfunctional JOINS, what am I missing? How should this logic be built?
I don't think you need Left Joins, but you probably need a NOT EXISTS the following query should do it:
SELECT u.* FROM users u
INNER JOIN user_orders uo ON u.id=uo.user_id
INNER JOIN books b ON uo.book_id=b.id
INNER JOIN authors_books ab ON ab.book_id = b.id
INNER JOIN authors a ON ab.author_id=a.id
WHERE a.name = 'Steven King'
AND NOT EXISTS (
SELECT 1 FROM users u2
INNER JOIN user_orders uo2 ON u2.id=uo2.user_id AND uo2.user_id = uo.user_id
INNER JOIN books b2 ON uo2.book_id=b2.id
INNER JOIN authors_books ab2 ON ab2.book_id = b2.id
INNER JOIN authors a2 ON ab2.author_id=a2.id
WHERE a2.name = 'Shakespear')
Without sample data I can't test this. It may be able to be simplified as it looks a bit ugly at the moment, but it should give you a starting point.
Join 5 tables, group by user and set the conditions in the HAVING clause:
select
u.id, u.first_name, u.last_name
from users u
inner join user_orders o on o.user_id = u.id
inner join books b on b.id = o.book_id
inner join authors_books ab on ab.book_id = b.id
inner join authors a on a.id = ab.author_id
group by u.id, u.first_name, u.last_name
having sum(a.name = 'Stephen King') > 0 and sum(a.name = 'William Shakespeare') = 0

issue selecting from many-to-many

my db is very simple:
CREATE TABLE Account (
accountId int NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
PRIMARY KEY (accountId)
);
CREATE TABLE Manager (
managerId int NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
PRIMARY KEY (managerId)
);
CREATE TABLE ManagerAccount(
id int not null auto_increment,
managerId int not null,
accountId int not null,
primary key(id),
foreign key(managerid) references Manager (ManagerID),
foreign key(accountId) references Account (AccountID)
);
Now, when i return an account object to the user I need to pull all the account associated with specific account
So I did something like:
select m.name
from manager m
inner join ManagerAccount ma on m.managerId = ma.id
inner join Account a on ma.id = a.accountId
where a.accountId = 1;
but this does not give me the answer I want, I only get one manager name and there are 3 managers associated with accountId 1...
you can see here:
inner join ManagerAccount ma on m.managerId = ma.id
inner join Account a on ma.id = a.accountId
should be
inner join ManagerAccount ma on m.managerId = ma.managerId
inner join Account a on ma.accountId = a.accountId
In your query, you have an foreign key relationship of ManagerAccount.accountId with Account (AccountID).
But you have mapped the primary key of ManagerAccount table with Account (AccountID).
SELECT
m.name
FROM
Manager m
INNER JOIN
ManagerAccount ma ON m.managerId = ma.managerId
INNER JOIN
Account a ON ma.accountId = a.accountId
WHERE
a.accountId = 1;

MySQL: Count in other tables

in my MySQL database I have three tables:
CREATE TABLE favorites (
id int(11) NOT NULL AUTO_INCREMENT,
user_id int(11) NOT NULL,
location_id int(11) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE locations (
id int(20) NOT NULL,
`name` varchar(150) NOT NULL,
pos_lat float NOT NULL,
pos_lon float NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE ratings (
id int(11) NOT NULL AUTO_INCREMENT,
location_id int(11) NOT NULL,
user_id int(11) NOT NULL
stars int(11) NOT NULL,
review text,
PRIMARY KEY (id)
);
Now I want to select some of the locations and calculate the number of ratings, the average number of stars and the number of favorites in an efficient way.
My approach is this one but it gives me totally wrong values for the COUNTs.
SELECT l.id AS location_id,
COUNT(DISTINCT r.id), AVG(r.stars), COUNT(DISTINCT f.id)
FROM locations l, ratings r, favorites f
WHERE (l.id=r.location_id OR l.id=f.location_id)
AND l.id IN (7960,23713,...,18045,24247)
GROUP BY l.id
Can you help me?
The problem has to do with your join condition using OR:
WHERE (l.id=r.location_id OR l.id=f.location_id)
When it finds ONE record where l.id = r.location_id it will be true for ALL rows in f because of the OR. Similarly when it finds 1 record with l.id = f.location_id you will match ALL rows in r.
Instead, use a LEFT JOIN for each of r and f:
SELECT l.id AS location_id,
COUNT(DISTINCT r.id), AVG(r.stars), COUNT(DISTINCT f.id)
FROM locations l
LEFT JOIN ratings r ON (l.id = r.location_id)
LEFT JOIN favorites f ON (l.id = f.location_id)
WHERE l.id IN (7960,23713,...,18045,24247)
GROUP BY l.id