I'm trying to make a cookbook and I'm having an issue with duplication. It will be easier to understand with an example, so let's begin:
My tables look like this:
create table Recipe (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(25),
description VARCHAR(50),
instructions VARCHAR(500))
ENGINE=InnoDB DEFAULT CHARSET=utf8;
create table Ingredient (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50))
ENGINE=InnoDB DEFAULT CHARSET=utf8;
create table Measure (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30))
ENGINE=InnoDB DEFAULT CHARSET=utf8;
create table RecipeIngredient (
recipe_id INT NOT NULL,
ingredient_id INT NOT NULL,
measure_id INT,
amount INT,
CONSTRAINT fk_recipe FOREIGN KEY(recipe_id) REFERENCES Recipe(id),
CONSTRAINT fk_ingredient FOREIGN KEY(ingredient_id) REFERENCES Ingredient(id),
CONSTRAINT fk_measure FOREIGN KEY(measure_id) REFERENCES Measure(id))
ENGINE=InnoDB DEFAULT CHARSET=utf8;
and when I run a query that looks like this:
SELECT r.name AS 'Recipe',
r.instructions,
ri.amount AS 'Amount',
mu.name AS 'Unit of Measure',
i.name AS 'Ingredient'
FROM Recipe r
JOIN RecipeIngredient ri on r.id = ri.recipe_id
JOIN Ingredient i on i.id = ri.ingredient_id
LEFT OUTER JOIN Measure mu on mu.id = measure_id;
I'm getting this result :
The problem is that Chocolate Cake is duplicating even though it's the same recipe it just has more than one ingredient. Could you please help me solve this problem, so I would get one instance of Recipe?
Use GROUP_CONCAT to combine values from multiple rows in a group into a comma-separated list. And use CONCAT to concatenate strings, like concatenating the amount to the unit and ingredient.
SELECT r.name, r.instructions,
GROUP_CONCAT(CONCAT(ri.amount, mu.name, ' ', i.name)) AS ingredients
FROM Recipe r
JOIN RecipeIngredient ri on r.id = ri.recipe_id
JOIN Ingredient i on i.id = ri.ingredient_id
LEFT OUTER JOIN Measure mu on mu.id = measure_id
GROUP BY r.id;
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
first post here!
So as a young Padawan in SQL and Databases in general I am working on creating a database for a business to manage orders/items/prices etc. I want to create a View that from 3 linked tables {items,prices,discounts}(see tables at the bottom) calculates and show the total price of an item. As a rule, the discount column shouldn't be zero( because we only want in the table discounts entries with actual discount , not 0 )
From the following entries I want to show all of them but my view only shows the one's with discounts.
insert into items (`item_id`,`item_name`, `item_quantity`) values
(102,'item1',20),
(103,'item2',20),
(404,'item3',20); # <-- It won't be shown if I do SELECT * FROM view;
insert into discounts (`item_id`,`discount`) values
(102,50),
(103,25);
insert into prices (`item_id`,`price`) values
(102,100),
(103,100),
(404,100);
And here is my View:
CREATE VIEW ItemsPrice AS
SELECT
i.item_id,
i.item_name,
SUM((1-d.discount/100)*p.price*i.item_quantity)
FROM
items AS i
INNER JOIN
prices AS p ON i.item_id=p.item_id
INNER JOIN
discounts AS d ON (p.item_id=d.item_id)
GROUP BY item_id
ORDER BY total;
Here are my tables (just in case I made them wrong):
DROP TABLE IF EXISTS `items`;
CREATE TABLE `items` (
`item_id` int(30) NOT NULL,
`item_name` varchar(35) NOT NULL,
`item_quantity` double(25,0) ,
PRIMARY KEY (`item_id`)
);
#2=======
DROP TABLE IF EXISTS `prices`;
CREATE TABLE `prices` (
`item_id`int(30) NOT NULL,
`price` decimal(30,2) NOT NULL,
PRIMARY KEY (`item_id`),
CONSTRAINT `prices_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `items` (`item_id`)
);
#3=======
DROP TABLE IF EXISTS `discounts`;
CREATE TABLE `discounts` (
`item_id` int(30) NOT NULL,
`discount` int(3) NOT NULL,
PRIMARY KEY (`item_id`),
CONSTRAINT `discount_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `items` (`item_id`)
);
Sorry for not providing a schema. Don't know how to make one.
Hope I didn't waste much of your time! You are my hero.
You just need a left join - this will grab all items with prices even of they do not have a discount. You use COALESCE to replace the NULL discounts with a valid number.
SELECT
i.item_id,
i.item_name,
COALESCE(d.discount,0) as discount,
p.price,
i.item_quantity
FROM
items AS i
INNER JOIN
prices AS p ON i.item_id=p.item_id
LEFT JOIN
discounts AS d ON (p.item_id=d.item_id)
GROUP BY item_id
ORDER BY total;
Your query with COALESCE:
SELECT
i.item_id,
i.item_name,
(1-COALESCE(d.discount,0)/100)*p.price*i.item_quantity) as totalAmount
FROM
items AS i
INNER JOIN
prices AS p ON i.item_id=p.item_id
LEFT JOIN
discounts AS d ON (p.item_id=d.item_id)
GROUP BY item_id
ORDER BY total;
I am trying to execute the following query
SELECT `id`,
`name`,
`ownerid`,
`creationdata`,
`motd`,
(SELECT Count(*)
FROM guild_membership a,
players_online b
WHERE a.player_id = b.player_id
AND a.guild_id = id) AS `online`,
(SELECT Max(b.level)
FROM guild_membership a,
players b
WHERE a.player_id = b.id
AND a.guild_id = id) AS `toplevel`,
(SELECT Min(a.level)
FROM players a,
guild_membership b
WHERE a.id = b.player_id
AND b.guild_id = id) AS `lowlevel`
FROM `guilds`
WHERE `name` = 'Wideswing Poleaxe'
LIMIT 1;
The tables used in here are the followin
CREATE TABLE IF NOT EXISTS `players` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`group_id` int(11) NOT NULL DEFAULT '1',
`account_id` int(11) NOT NULL DEFAULT '0',
`level` int(11) NOT NULL DEFAULT '1',
...
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE,
KEY `vocation` (`vocation`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `guilds` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`ownerid` int(11) NOT NULL,
`creationdata` int(11) NOT NULL,
`motd` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY (`name`),
UNIQUE KEY (`ownerid`),
FOREIGN KEY (`ownerid`) REFERENCES `players`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `guild_membership` (
`player_id` int(11) NOT NULL,
`guild_id` int(11) NOT NULL,
`rank_id` int(11) NOT NULL,
`nick` varchar(15) NOT NULL DEFAULT '',
PRIMARY KEY (`player_id`),
FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`rank_id`) REFERENCES `guild_ranks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
I am trying to get the MAX level and MIN level on the players table inside one guild
However I am always getting toplevel and lowlevel the same value and tis always the lowest level
I am not sure what is wrong
First thing I notice is: you are using LIMIT without ORDER BY. So from the guilds table you expect to find more than one entry for name = 'Wideswing Poleaxe', but only look at the first the DBMS happens to find. Is this desired?
Next thing I see is the out-dated join syntax. Where did you get this from? A twenty year old book? No, stop, twenty years ago this syntax was already made redundant, so it must be even older ;-) Use explicit joins instead (JOIN ... ON ...)
As to your subqueries: You are comparing with id without any qualifier, so the DBMS will take this to be guild_membership.id or players_online resp. players.id, where you really want it to be guild.id. This should explain that you get unexpected values.
As to how the query is built: You could join to the aggregated player data instead. And use alias names that match the tables.
select
guilds.id,
guilds.name,
guilds.ownerid,
guilds.creationdata,
guilds.motd,
players.online,
players.toplevel,
players.lowlevel
from guilds
left join
(
select
gms.guild_id,
max(p.level) as toplevel,
min(p.level) as lowlevel,
sum((select count(*) from players_online po where po.player_id = p.id)) as online
from guild_membership gms
join players p on p.id = gms.player_id
group by gms.guild_id
) players on players.guild_id = guilds.id
where guilds.name = 'Wideswing Poleaxe';
You can change the left outer join (left join) to an inner join (join), if you don't need to see guilds without any player.
I think the problem is here: a.guild_id = id
The id being used is from players, not guilds, as it is still part of the sub-query.
You shouldn't need all those subqueries, JOINs are almost always faster and should usually be first technique tried.
Try this...
SELECT `id`, `name`, `ownerid`, `creationdata`, `motd`
, COUNT(po.player_id) AS online
, MAX(p.level) AS toplevel
, MIN(p.level) AS lowlevel
FROM `guilds` AS g
LEFT JOIN guild_membership AS gm ON g.id = gm.guild_id
LEFT JOIN players AS p ON gm.player_id = p.player_id
LEFT JOIN players_online AS po ON gm.player_id = po.player_id
WHERE g.`name` = 'Wideswing Poleaxe'
;
COUNT only counts non-null values; similarly MAX, MIN, and most other aggregate functions ignore null values (only returning null if only null values were processed).
You should consider modifying your query like
SELECT g.`id`,
g.`name`,
g.`ownerid`,
g.`creationdata`,
g.`motd`,
(SELECT Count(*)
FROM guild_membership a,
players_online b
WHERE a.player_id = b.player_id
AND a.guild_id = id) AS `online`,
(SELECT Max(b.level)
FROM players b join guild_membership a on a.player_id = b.id
AND a.guild_id = g.id) AS `toplevel`,
(SELECT Min(a.level)
FROM players a join
guild_membership b on a.id = b.player_id
AND b.guild_id = g.id) AS `lowlevel`
FROM `guilds` g
WHERE g.`name` = 'Wideswing Poleaxe'
LIMIT 1;
I have the following tables.
CREATE TABLE `Customer` (
`CID` varchar(10) CHARACTER SET latin1 NOT NULL DEFAULT '',
`Name` varchar(40) CHARACTER SET latin1 NOT NULL DEFAULT '',
`City` varchar(40) CHARACTER SET latin1 NOT NULL DEFAULT '',
`State` varchar(40) CHARACTER SET latin1 NOT NULL DEFAULT '',
PRIMARY KEY (`CID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
CREATE TABLE `LineItem` (
`LID` varchar(10) NOT NULL DEFAULT '',
`OID` varchar(10) NOT NULL DEFAULT '',
`PID` varchar(110) NOT NULL DEFAULT '',
`Number` int(11) DEFAULT NULL,
`TotalPrice` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`LID`),
KEY `Order ID` (`OID`),
CONSTRAINT `Order ID` FOREIGN KEY (`OID`) REFERENCES `OrderItem` (`OID`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `OrderItem` (
`OID` varchar(10) NOT NULL DEFAULT '',
`CID` varchar(10) NOT NULL DEFAULT '',
PRIMARY KEY (`OID`),
KEY `CID` (`CID`),
CONSTRAINT `CID` FOREIGN KEY (`CID`) REFERENCES `Customer` (`CID`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `Product` (
`PID` varchar(10) NOT NULL DEFAULT '',
`ProductName` varchar(40) DEFAULT '',
`Price` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`PID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
What I've been trying to do in my query is run it so I can successfully get it to do the following:
List the products bought by all the customers of Newark
List the products ordered only by the customers of Newark
For #5, I tried this query:
Select product.productname
From Customer as c INNER JOIN OrderItem as o
ON c.CID = o.CID
INNER JOIN LineItem line
ON o.OID = line.OID
Inner Join Product product
ON line.PID = product.PID
Where C.City = 'Newark'
Having Count(product.productname) > 1;
But it only returns one value and it should return 2 (unless I am not using it properly).
For #6 I understand the concept but I don't know how to "subtract tables" in SQL.
The goal of the first question is to list the common items purchased by everyone from Newark. So if Person A bought Items X, Y and Z and Person B bought W, V, and Y, the query will return "Item Y".
I guess my comment is an answer.
Having Count(product.productname) > 1;
Having requires a group by to function correctly as it's a filter on an aggregate and aggregates require a group by. 90% of database engines would have returned an error explicitly stating it requires a group by...but MySQL prefers to do the wrong thing instead of return an error to you (it's why you got one row...MySQL did a group by of whatever it felt like). Add a group by (I assume on product name with what you have here) and it should work.
you need to add GROUP BY . Try this:
Select product.productname
From Customer as c
INNER JOIN OrderItem as o ON c.CID = o.CID
INNER JOIN LineItem line ON o.OID = line.OID
Inner Join Product product ON line.PID = product.PID
Where C.City = 'Newark'
Group by product.productname
Having Count(*) > 1;
Two other answers have pointed out that if you want the HAVING then you need a GROUP BY. But your question doesn't actually ask a question or explain what your query is supposed to return. (You are not explaining clearly in your question or comments.) You wrote a comment, "I have two people from Newark and was trying to show the item(s) [product(s)?] that both of them purchased." But if your query is only "corrected" with grouping then it calculates the wrong counts.
A problem is that you should return PID (and maybe ProductName). You need to list products. PIDs as key are presumably 1:1 with products but ProductName is not a key so product names are not 1:1 with products. ProductName can even be NULL. So selecting ProductName does not get you all the relevant products. (Also in LineItem PID should be a FOREIGN KEY.)
Another problem is that you should use PID to GROUP BY product. ProductName is not a key of Product. So two products can have the same name. So you will get the count for each product name, not for each product. Plus ProductName can be NULL. (Even if you were only returning the names not PIDs of products with those counts, you would need to group by PID.)
Another problem is the counts are wrong. Grouping by PID groups rows that can be made by combining a Newark Customer row, an OrderItem row and a LineItem row for a product. Those combination rows are counted by COUNT(PID). But you want the number of disinct Newark customers in those rows. You could do this by a sub-select but there happens to be a shorter way.
SELECT p.PID, p.ProductName
FROM Customer c
JOIN OrderItem AS o ON c.CID = o.CID
JOIN LineItem l ON o.OID = l.OID
JOIN Product p ON l.PID = p.PID
WHERE c.City = 'Newark'
GROUP BY (p.PID)
HAVING COUNT(DISTINCT c.CID) > 1;
You get a query for goal 1 after a change in part of its condition.
HAVING COUNT(DISTINCT c.CID)
= (SELECT COUNT(*) FROM Customer WHERE City='Newark')
Relational algebra MINUS/DIFFERENCE corresponds to EXCEPT in the SQL standard. But MySQL does not have it. You can do it using LEFT JOIN, NOT IN or NOT EXISTS instead.
My code looks like this
CREATE TABLE Genre (
genreID INT NOT NULL DEFAULT 0,
genreName VARCHAR(20) NULL,
PRIMARY KEY (genreID));
CREATE TABLE Artists (
ArtistID INT NOT NULL DEFAULT 0,
name VARCHAR(45) NULL,
Genre_genreID INT NOT NULL,
PRIMARY KEY (ArtistID),
FOREIGN KEY (Genre_genreID)
REFERENCES Genre(genreID));
CREATE TABLE Albums (
albumsID INT NOT NULL DEFAULT 0,
name VARCHAR(45) NULL,
Artists_ArtistID INT NOT NULL,
PRIMARY KEY (albumsID),
FOREIGN KEY (Artists_ArtistID)
REFERENCES Artists(ArtistID));
CREATE TABLE Songs (
songID INT NOT NULL,
name VARCHAR(45) NULL,
length TIME NULL,
Albums_albumsID INT NOT NULL DEFAULT 0,
PRIMARY KEY (songID),
FOREIGN KEY (Albums_albumsID)
REFERENCES Albums (albumsID));
SELECT Artists.name, Genre.genreName, Songs.name
FROM Songs
INNER JOIN Genre ON Artists.ArtistID=Genre.genreID
INNER JOIN Artists ON Albums.Artists_ArtistID=Artists.ArtistID
INNER JOIN Albums ON Songs.Albums_albumID=Albums.albumsID;
Looking to try and get the name of the artists, genre and song to all match up and display. Yet I get
Unknown column 'Artists.ArtistID' in 'on clause'
Im fairy new to SQL and to INNER JOINS any help and explanations would be great!
Try this
SELECT Artists.name, Genre.genreName, Songs.name
FROM Songs
INNER JOIN Albums ON Songs.Albums_albumID=Albums.albumsID
INNER JOIN Artists ON Albums.Artists_ArtistID=Artists.ArtistID
INNER JOIN Genre ON Artists.ArtistID=Genre.genreID;
check the sequence and occurrence of tables in join
you are mention the Songs table in first row joining. but your joining the table of ON function in last line only like your code, you should try this method:
SELECT
Artists.name, Genre.genreName, Songs.name
FROM Songs
INNER JOIN Albums
ON Songs.Albums_albumID=Albums.albumsID
INNER JOIN Artists
ON Albums.Artists_ArtistID=Artists.ArtistID
INNER JOIN Genre
ON Artists.ArtistID=Genre.genreID;