Select all categories with topic and post count - mysql

I have categories with id, topics with id and category, and posts with id and topic. I'd like to list categories along with the number of topics belonging to each category as well as number of posts belonging to topics belonging to those categories.
what I'm using thus far to gather categories and their respective topic counts
select c.*, count(t.id) topics
from categories c
join topics t
on t.category=c.id
group by c.id
I've tried the following, but it just gives me the same post and topic count
select c.*, count(t.id) topics, count(p.id) posts
from categories c
join topics t
on t.category=c.id
join posts p
on p.topic=t.id
group by c.id
trying left join doesn't seem to make any difference

Use COUNT(DISTINCT t.id) to only count the unique topic ids per category.
select c.*, count(DISTINCT t.id) topics, count(p.id) posts
from categories c
join topics t
on t.category=c.id
join posts p
on p.topic=t.id
group by c.id
Alternatively, you can use subqueries:
select c.*,
count(t.id) topics,
( select count(p.id)
from posts p
where p.topic = t.id
) posts
from categories c
join topics t
on t.category=c.id
group by c.id
or even
select c.*,
( select count(t.id)
from topics t
where t.category = c.id) topics,
( select count(p.id)
from topics t
join posts p
on p.topic = t.id
where t.category = c.id) posts
from categories c
In this particular case count(distinct) is obviously the easiest, but in other cases, subqueries can give you more possibilities.

Related

Add count value to a SQL query?

I am making a forum page using MySQL as database, but I'm coming from MongoDB and am a bit confused. When I fetch all posts for a specific category it looks something like this
SELECT p.id, p.posted_at, p.title, p.content, c.name AS category_name, u.name
AS author_name
FROM posts AS p
INNER JOIN users AS u ON p.author = u.id
INNER JOIN categories AS c ON p.category = c.id
WHERE p.category = 3 <-- Category ID
People can follow posts so I have a table called user_post_relations which contains two columns; user_id and post_id.
My goal is to add a follower count per post to the query that's getting all the posts per category. How can this be achieved with only one query?
Add the following JOIN with sub-query to your query
JOIN (SELECT post_id, COUNT(*) follower_count
FROM user_post_relations
GROUP BY post_id) AS upr ON upr.post_id = p.id
And then add upr.follower_count to your SELECT list

How can I add multiple tables via LEFT JOIN and count the rows of each table?

I am counting the results of a table I LEFT JOINED:
SELECT p.*,COUNT(po.name) AS posts
FROM projects p
left join posts po on p.name = po.name
group by p.id
http://sqlfiddle.com/#!9/3e9d4b/4
But now I want to add another table via LEFT JOIN and count it also:
SELECT p.*,COUNT(po.name) AS posts,
COUNT(ta.name) AS tasks
FROM projects p
left join posts po on p.name = po.name
left join tasks ta on p.name = ta.name
group by p.id
http://sqlfiddle.com/#!9/ee068/2
But now the counting is wrong. For cat I have only 2 posts and 3 tasks. Where is the number 6 coming from?
Left joins are not the right tool for this. You should use subselects:
SELECT p.*,
(SELECT COUNT(*) FROM posts po WHERE p.name = po.name) AS posts,
(SELECT COUNT(*) FROM tasks ta WHERE p.name = ta.name) AS tasks
FROM projects p
You can still use joins, but instead of a single top level aggregation, you can aggregate each of the two tables in separate subqueries:
SELECT
p.name,
COALESCE(t1.posts_cnt, 0) AS posts_cnt,
COALESCE(t2.tasks_cnt, 0) AS tasks_cnt
FROM projects p
LEFT JOIN
(
SELECT name, COUNT(*) AS posts_cnt
FROM posts
GROUP BY name
) t1
ON p.name = t1.name
LEFT JOIN
(
SELECT name, COUNT(*) AS tasks_cnt
FROM tasks
GROUP BY name
) t2
ON p.name = t2.name
Your current queries have problems, because you are aggregating by id but selecting other columns. From what I see, you want to be aggregating in all three tables using the name column.
This approach should outperform an approach using correlated subqueries.

Getting most recent record through another related table

How would I create a MySQL command to get the most recent categories that have had a post favorited?
I am attempting to join the tables and group them, but am unsure how to limit the results to a maximum number of categories.
So to give a simple example of what I'm trying to do:
Users (id)
Favorites (id, last_updated, post_id, user_id)
Posts (id, title, category_id)
Categories (id, name)
And then for sql:
SELECT favorites.*, posts.category_id
FROM favorites
INNER JOIN posts ON favorites.user_id = 1 AND favorites.post_id = posts.id
INNER JOIN categories ON posts.category_id = categories.id
GROUP BY categories.id, favorites.id
ORDER BY favorites.last_updated DESC
Obviously, this statement is not working as intended, as it returns many results for each category, instead of one for each category - the most recent.
You can't GROUP BY a field not present in SELECT.
Here is a solution assuming that you want this only for user_id = 1:
SELECT c.id, MAX(f.last_updated)
FROM favorites f
INNER JOIN posts p ON (f.user_id = 1 AND f.post_id = p.id)
INNER JOIN categories c ON (p.category_id = c.id)
GROUP BY c.id
ORDER BY MAX(f.last_updated) DESC
With this you get the IDs of the desired categories. You can join the result with categories again to get the rest of the columns.

MySQL Join Question

Hi i'm struggling to write a particular MySQL Join Query.
I have a table containing product data, each product can belong to multiple categories. This m:m relationship is satisfied using a link table.
For this particular query I wish to retrieve all products belonging to a given category, but with each product record, I also want to return the other categories that product belongs to.
Ideally I would like to achieve this using an Inner Join on the categories table, rather than performing an additional query for each product record, which would be quite inefficient.
My simplifed schema is designed roughly as follows:
products table:
product_id, name, title, description, is_active, date_added, publish_date, etc....
categories table:
category_id, name, title, description, etc...
product_category table:
product_id, category_id
I have written the following query, which allows me to retrieve all the products belonging to the specified category_id. However, i'm really struggling to work out how to retrieve the other categories a product belongs to.
SELECT p.product_id, p.name, p.title, p.description
FROM prod_products AS p
LEFT JOIN prod_product_category AS pc
ON pc.product_id = p.product_id
WHERE pc.category_id = $category_id
AND UNIX_TIMESTAMP(p.publish_date) < UNIX_TIMESTAMP()
AND p.is_active = 1
ORDER BY p.name ASC
I'd be happy just retrieving the category id's releated to each returned product row, as I will have all category data stored in an object, and my application code can take care of the rest.
Many thanks,
Richard
SELECT p.product_id, p.name, p.title, p.description,
GROUP_CONCAT(otherc.category_id) AS other_categories
FROM prod_products AS p
JOIN prod_product_category AS pc
ON pc.product_id = p.product_id
LEFT JOIN prod_product_category AS otherc
ON otherc.product_id = p.product_id AND otherc.category_id != pc.category_id
WHERE pc.category_id = $category_id
AND UNIX_TIMESTAMP(p.publish_date) < UNIX_TIMESTAMP()
AND p.is_active = 1
GROUP BY p.product_id
ORDER BY p.name ASC
You would use an inner join to the product_category table, doing a left join there is pointless as you are using the value from it in the condition. Then you do a left join on the product_category table to get the other categories, and join in the categories for the data:
select
p.product_id, p.name, p.title, p.description,
c.category_id, c.name, c.title
from
prod_products p
inner join prod_product_category pc on pc.product_id = p.product_id
left join prod_product_category pc2 on pc2.product_id = p.product_id
left join prod_categories c on c.category_id = pc2.category_id
where
pc.category_id = #category_id and
unix_timestamp(p.publish_date) < unix_timestamp() and
p.is_active = 1
order by
p.name

Counting all the posts belonging to a category AND its subcategories

I would really appreciate some help with my problem:
I have 2 MySQL tables, categories and posts, laid out (simplified) like so:
categories:
CATID - name - parent_id
posts:
PID - name - category
What I would like to do is get the total amount of posts for each category, including any posts in subcategories.
Right now I am getting the total number of posts in each (top-level) category (but not subcategories) by doing:
"SELECT c.*, COUNT(p.PID) as postCount
FROM categories AS c LEFT JOIN posts AS p
ON (c.CATID = p.category)
WHERE c.parent='0' GROUP BY c.CATID ORDER BY c.name ASC";
The question once again is, how can I get the sum totals for each category including the totals for each related subcategory?
Restructuring the database to a nested set format is not possible, as I am maintaining an existing system.
Thanks for your help!
If the categories are not nested infinitely, you can JOIN them one level at a time. Here's an example for up to 3 levels of nesting:
SELECT c.name, COUNT(DISTINCT p.PID) as postCount
FROM categories AS c
LEFT JOIN categories AS c2
ON c2.parent = c.catid
LEFT JOIN categories AS c3
ON c3.parent = c2.catid
LEFT JOIN posts AS p
ON c.CATID = p.category
OR c2.CATID = p.category
OR c3.CATID = p.category
WHERE c.parent = '0'
GROUP BY c.CATID, c.name
ORDER BY c.name ASC
I think you want to look at the Rollup operator. I believe this will get you what you want.
http://msdn.microsoft.com/en-us/library/ms189305(SQL.90).aspx
Randy