MySQL multi-level parent selection/join question - sql

I have a hopefully simple MySQL query question which is eluding me at late at night. I'm trying to do a SELECT which counts the number of instances of a set of data (orders) and groups those instances by a value which exists in a parent a couple levels above the order itself.
For example:
CREATE TABLE `so_test`.`categories` (
`id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=572395 DEFAULT CHARSET=latin1;
CREATE TABLE `so_test`.`product_group` (
`id` int(10) unsigned NOT NULL auto_increment,
`category_id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=572395 DEFAULT CHARSET=latin1;
CREATE TABLE `so_test`.`products` (
`id` int(10) unsigned NOT NULL auto_increment,
`product_group_id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=572395 DEFAULT CHARSET=latin1;
CREATE TABLE `so_test`.`orders` (
`id` int(10) unsigned NOT NULL auto_increment,
`product_id` int(10) unsigned NOT NULL auto_increment,
`customer_id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=572395 DEFAULT CHARSET=latin1;
What I'm looking to do is something in the neighborhood of:
SELECT count(orders.id), categoryId
FROM orders, categories WHERE
orders.customer_id in (1,2,3) GROUP BY orders.productId.productGroupId.categoryId
Assuming there are 17 orders for products in category 1, 2 orders for products in category 2, and 214 orders for category 3, what I'm hoping to get back is:
count(orders.id), categoryId
============================
17 1
2 2
214 3
If I was trying to group by say product_id I'd be fine..but the two-levels-up portion is throwing me.
Thanks!

Just join them together:
select categoryid, count(orders.id)
from category c
left join product_group pg on pg.category_id = c.id
left join products on p on p.product_group_id = pg.id
left join orders o on o.product_id = p.id
For categories without an order, count(orders.id) will return 0, while count(*) would return one or more, depending on the number of productgroups and products.
An inner join would not count categories without orders at all.

Related

Groupby Count Query On Two Tables Filtered by Condition on Third Table

I have three tables: Category, Mail and Classification. The relationship between the tables is outlined in this SQLFiddle with some sample data: http://sqlfiddle.com/#!9/118b24/3/0
CREATE TABLE `Category` (
`id` int(6) unsigned NOT NULL,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE `Mail` (
`id` int(6) unsigned NOT NULL,
`content` varchar(500) NOT NULL,
`date` datetime NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE `Classification` (
`id` int(6) unsigned NOT NULL,
`mail_id` int(6) unsigned NOT NULL,
`category_id` int(6) unsigned NOT NULL,
FOREIGN KEY (mail_id) REFERENCES Mail(id),
FOREIGN KEY (category_id) REFERENCES Category(id),
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
I first run a query to get the count of emails assigned to each category:
SELECT Category.name, count(Classification.category_id) FROM Category LEFT OUTER JOIN Classification ON Classification.category_id = Category.id GROUP BY Category.name
Which works fine.
But I would now like to add a filter based on the date that is in the Email collection. If I had a join with a filter collection:
SELECT Category.name, count(Classification.category_id) FROM Category JOIN Mail ON Mail.date < '2019-03-24' LEFT OUTER JOIN Classification ON Classification.category_id = Category.id GROUP BY Category.name
But now the count only doubles and it seems the filter isn't even applied. Why isn't the filter working and what should I do to fix it?
You need a JOIN condition between Classification and Mail:
SELECT ca.name, count(cl.category_id)
FROM Category LEFT JOIN
Classification cl
ON cl.category_id = c.id LEFT JOIN
Mail m
ON m.id = cl.mail_id AND -- your JOIN key goes here
m.date < '2019-03-24'
GROUP BY c.name ;

SQL-Statement for counting and calculating relational data

I have a main-table "Restaurants" and an one-to-many relation to a "RestaurantVotes" table. One Restaurant can have multiple Votes. It's defined like this:
CREATE TABLE `restaurant_votes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_at` datetime NOT NULL,
`user_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`cat_food` int(11) NOT NULL,
`cat_cart` int(11) NOT NULL,
`cat_ambassador` int(11) NOT NULL,
`cat_drinks` int(11) NOT NULL,
`cat_service` int(11) NOT NULL,
`cat_ambience` int(11) NOT NULL,
`cat_price` int(11) NOT NULL,
`restaurant_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `IDX_1B96C91EB1E7706E` (`restaurant_id`),
CONSTRAINT `FK_1B96C91EB1E7706E` FOREIGN KEY (`restaurant_id`) REFERENCES `restaurants` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `restaurants` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
For an API-call we need to get all restaurants including name and the overall rating for this restaurant as a JSON response. Name etc. is no the problem, but we have big problems to find a SQL-query which gets the rating for each restaurant. The rating is calculated like this:
Summerize the values (integer 1-5) of all categories (cat_*) and divide it by the number of the categories (7).
Summerize the result of the ratings above and divide the value by the number of ratings in the restaurant_votes table per restaurant.
Return the overall rating from 2. in the SQL-query together with the name of the restaurant.
Is this even possible using SQL? Any advice? Thanks!
select
r.name,
sum(rv.cat_food +
rv.cat_cart +
rv.cat_ambassador +
rv.cat_drinks +
rv.cat_service +
rv.cat_ambience +
rv.cat_price) / 7 as total_score,
sum(rv.cat_food +
rv.cat_cart +
rv.cat_ambassador +
rv.cat_drinks +
rv.cat_service +
rv.cat_ambience +
rv.cat_price) / 7 / count(rv.id) as average_score
from restaurants r
left join restaurant_votes rv
on r.id = rv.restaurant_id
group by r.name
I've posted a SQLFiddle to demonstrate this.
SELECT r.name,r.id,score.rating
FROM
restaurants as r
INNER JOIN
(
SELECT restaurant_id,AVG(cat_food + cat_cart + cat_ambassador + cat_drinks + cat_service + cat_ambience + cat_price)/7 AS rating
FROM restaurant_votes
GROUP BY restaurant_id
)as score
ON r.id = score.rating
Would this work?
It gets all the restaurants defined in the restaurant table and and gets the average of all the columns added together and divided by 7.

how to create a query two or more values?

I would need if I could help with the following code:
CREATE TABLE `car` (
`car_id` int(10) unsigned NOT NULL auto_increment,
`car_name` varchar(25) NOT NULL,
`car_year` date NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB;
CREATE TABLE `people` (
`peo_id` int(10) unsigned NOT NULL auto_increment,
`peo_name` varchar(50) NOT NULL,
`peo_surname` varchar(200) NOT NULL,
`car_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`fav_id`),
KEY `FK_Favorites` (`user_id`),
CONSTRAINT `FK_Favorites` FOREIGN KEY (`car_id`) REFERENCES `car` (`car_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
I have questions, show all cars that are more than 3 people ordered by year of car.
Thank you very much, sorry for my bad English
SELECT
c.car_id
,c.car_year
,COUNT(p.peo_id)
FROM car c
INNER JOIN people p on c.card_id = p.car_id
GROUP BY c.car_id ,c.car_year
HAVING COUNT(p.peo_id) > 3
ORDER BY c.car_year
First, find number of cars with more than three people
select car_id
from people
group by car_id
having count(*) > 3
Now, get the car details
select * from car
where car_id in
(
select car_id
from people
group by car_id
having count(*) > 3
)
order by car_year

Cant figure out how to join these tables

Need help with some kind of join here. Cant figure it out.
I want to loop out the forum boards, but I also want to get last_post and last_posterid from posts, and based on last_posterid get username from users.
This is how far I've come yet (:P):
SELECT name, desc, position FROM boards b
INNER JOIN posts p ON ???
INNER JOIN users u ON ???
ORDER BY b.position ASC
Help would be greatly appreciated.
Cheers
CREATE TABLE `posts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`posterid` int(10) unsigned NOT NULL DEFAULT '0',
`subject` varchar(255) DEFAULT NULL,
`message` text,
`posted` int(10) unsigned NOT NULL DEFAULT '0',
`edited` int(10) unsigned DEFAULT NULL,
`edited_by` int(10) unsigned NOT NULL DEFAULT '0',
`icon` varchar(255) DEFAULT NULL,
`topicid` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
CREATE TABLE `boards` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(200) NOT NULL,
`desc` varchar(255) NOT NULL,
`position` int(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
CREATE TABLE `users` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(200) NOT NULL,
`password` varchar(255) NOT NULL,
`salt` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
CREATE TABLE `topics` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`posterid` int(10) unsigned NOT NULL DEFAULT '0',
`subject` varchar(255) NOT NULL,
`posted` int(10) unsigned NOT NULL DEFAULT '0',
`last_post` int(10) unsigned NOT NULL DEFAULT '0',
`last_poster` int(10) unsigned NOT NULL DEFAULT '0',
`num_views` mediumint(8) unsigned NOT NULL DEFAULT '0',
`num_replies` mediumint(8) unsigned NOT NULL DEFAULT '0',
`closed` tinyint(1) NOT NULL DEFAULT '0',
`sticky` tinyint(1) NOT NULL DEFAULT '0',
`icon` varchar(40) DEFAULT NULL,
`boardid` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=16 DEFAULT CHARSET=utf8
Assuming posts.topicid refers to boards.id, and posts.posterid refers to users.id, try something like
SELECT b.name, d.desc, b.position
FROM boards b
LEFT JOIN posts p
ON b.id = p.topicid
LEFT JOIN users u
ON (p.posterid = u.id) AND (p.posted = (SELECT MAX(sp.posted) FROM posts sp GROUP BY))
ORDER BY
b.position ASC
Another remark: try to name your fields such that it is clear what they refer to, for instance:
CREATE TABLE `foo` (
foo_ID UNSIGNED INTEGER NOT NULL AUTO_INCREMENT,
-- more stuff
PRIMARY KEY (`foo_ID`)
);
This allows you to re-use the field name foo_ID in another table, which makes things very easy when performing a join.
Nothing stands out, so this is mostly a guess:
SELECT b.name, b.desc, b.position
FROM boards AS b
INNER JOIN posts AS p
ON b.id = p.topicid
INNER JOIN users AS u
ON p.posterid = u.id
ORDER BY b.position ASC
SELECT username, message, posts.id
FROM users, posts
WHERE posterid=users.id
GROUP BY username, message, posts.id
HAVING posted=MAX(posted)
This might just do to get each user's last post concisely.
Ok, lack of a schema not withstanding, it looks like you could use nested select. Something like:
DECLARE #posts TABLE
(
postdate datetime,
postid int,
boardid int
)
DECLARE #users TABLE
(
userid int,
username varchar(10)
)
DECLARE #boards TABLE
(
boardid int
)
INSERT INTO #boards(boardid) VALUES (1)
INSERT INTO #users (userid, username) values (1, 'Adam')
insert into #posts (postdate, postid, boardid) values (GETDATE(), 1, 1)
SELECT b.*, p.*
from #boards b
inner join
(
SELECT TOP 1 postdate, postid, boardid, username FROM #posts
inner join #users on userid = postid WHERE boardid = 1
order by postdate desc
) as p on p.boardid = b.boardid
Oviously this is heavily generalised and uses some hard-coded values, but hopefully it should give you an option for implementing it on your own tables.
Of course, I infer no correctness in my implementation of the spec - SQL is not my first language lol
Was going to post this as a comment but it was too verbose and messy.
you'll want to use OUTER JOINs because otherwise boards with no posts in them will not show up
any single query that links those four tables is going to be a beast - consider using VIEWs to simplify things
your initial question and the tables you posted leave things a bit ambiguous: do you already have last_post and last_poster filled out in topics? If so, then your primary targets for the join should be boards and topics, and since you're already storing redundant data (I assume for speed?), why don't you also put in a last_post_time or something, which would simplify things a lot and curb load (no need to join in posts at all)?
mediumints? why on Earth? (If you don't need the date, and you think it'll never wrap, I suppose you can use the hack of trusting that a later post will have a higher ID due to autoincrementing key...)

SQL: Subquery assistance

I'm trying to SELECT two values in one statement instead of two statements. The first statement counts how many entries won for a specific contest that has an id of 18. The second statement counts the total quantity of prizes for that contest.
Query 1:
SELECT
COUNT(*) FROM entries
WHERE entries.contest=18
AND entries.won=1
Query 2
SELECT SUM( quantity ) FROM prizes WHERE contest=18
I want to have both so I could compare them on the server-side and act upon that comparison.. So if we had say 3 entrants who won the 18th contest and the total quantity of prizes for the 18th contest is 5, do something...
Schema:
CREATE TABLE `entries` (
`id` int(2) unsigned NOT NULL auto_increment,
`message_id` bigint(8) unsigned default NULL,
`user` int(2) unsigned default NULL,
`username` varchar(50) NOT NULL,
`contest` int(2) unsigned default NULL,
`message` text,
`icon` text,
`twitter_url` text,
`follower` int(2) unsigned default NULL,
`entry_date` timestamp NOT NULL default '0000-00-00 00:00:00',
`won` int(1) default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
CREATE TABLE `prizes` (
`id` int(2) unsigned NOT NULL auto_increment,
`name` varchar(25) NOT NULL,
`contest` int(2) NOT NULL,
`quantity` int(2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
There is no users table because I control the entries, and I'm pretty sure I won't get any duplicates so that's why the user name, etc is stored for the entry row.
As the queries doesn't have anything in common at all, you would use two subqueries to get them in the same result:
select
(select count(*) from entries where contest = 18 and won = 1) as wins,
(select sum(quantity) from prizes where contest = 18) as quantity