Getting most recent record through another related table - mysql

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.

Related

How to count only certain rows in a GROUP BY in Rails

In the application I'm building, there are posts and tags, and they are connected through a many-to-many relation. What I want to do is show all tags to the user and sort them by how many published posts they have (which is determined by the is_published column in the posts table).
Right now I'm sorting them by how many posts they have in general (both published and unpublished) with this code:
scope :top_used, -> { left_joins(:posts).group(:id).order("COUNT(posts.id) DESC") }
Which translates to the MySQL:
SELECT t.*
FROM tags t
LEFT
JOIN post_tags pt
ON pt.tag_id = tags.id
LEFT
JOIN posts p
ON p.id = pt.post_id
GROUP
BY t.id
ORDER
BY COUNT(p.id) DESC
So, again, I need to sort by the count of the number of published posts not all posts. Can this be done in MySQL?
Maybe this:
SELECT `tags`.* FROM `tags`
LEFT OUTER JOIN `post_tags` ON `post_tags`.`tag_id` = `tags`.`id`
LEFT OUTER JOIN `posts` ON `posts`.`id` = `post_tags`.`post_id` AND posts.is_published = 1
GROUP BY `tags`.`id`
ORDER BY COUNT(posts.id) DESC
?
Assuming that the column is_published's data type is Boolean or Integer, with values 1 or 0 you can order by the sum of the values:
ORDER BY SUM(posts.is_published) DESC
If is_published is nullable, use COALESCE():
ORDER BY COALESCE(SUM(posts.is_published), 0) DESC

How to properly join these three tables in SQL?

I'm currently creating a small application where users can post a text which can be commented and the post can also be voted (+1 or -1).
This is my database:
Now I want to select all information of all posts with status = 1 plus two extra columns: One column containing the count of comments and one column containing the sum (I call it score) of all votes.
I currently use the following query, which correctly adds the count of the comments:
SELECT *, COUNT(comments.fk_commented_post) as comments
FROM posts
LEFT JOIN comments
ON posts.id_post = comments.fk_commented_post
AND comments.status = 1
WHERE posts.status = 1
GROUP BY posts.id_post
Then I tried to additionally add the sum of the votes, using the following query:
SELECT *, COUNT(comments.fk_commented_post) as comments, SUM(votes_posts.type) as score
FROM posts
LEFT JOIN comments
ON posts.id_post = comments.fk_commented_post
AND comments.status = 1
LEFT JOIN votes_posts
ON posts.id_post = votes_posts.fk_voted_post
WHERE posts.status = 1
GROUP BY posts.id_post
The result is no longer correct for either the votes or the comments. Somehow some of the values seem to be getting multiplied...
This is probably simpler using correlated subqueries:
select p.*,
(select count(*)
from comments c
where c.fk_commented_post = p.id_post and c.status = 1
) as num_comments,
(select sum(vp.type)
from votes_posts vp
where c.fk_voted_post = p.id_post
) as num_score
from posts p
where p.status = 1;
The problem with join is that the counts get messed up because the two other tables are not related to each tother -- so you get a Cartesian product.
You want to join comments counts and votes counts to the posts. So, aggregate to get the counts, then join.
select
p.*,
coalesce(c.cnt, 0) as comments,
coalesce(v.cnt, 0) as votes
from posts p
left join
(
select fk_commented_post as id_post, count(*) as cnt
from comments
where status = 1
group by fk_commented_post
) c on c.id_post = p.id_post
left join
(
select fk_voted_post as id_post, count(*) as cnt
from votes_posts
group by fk_voted_post
) v on v.id_post = p.id_post
where p.status = 1
order by p.id_post;

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

mySQL JOIN wont return results with 0 count

SELECT categories.*, COUNT(categoryID) AS kritCount
FROM categories AS categories
LEFT JOIN krits ON categories.id = categoryID
WHERE (krits.approved = '1')
GROUP BY categories.id
So this works great except that it does not return a category that has a 0 count of krits in the category.
It will if I remove the WHERE statement but I need the WHERE to only select the krits where the field approved = 1
Any time you reference a column from a left joined table in the where clause (other than testing for NULL values), you force that join to behave like an inner join. Instead, move your test out of the where clause and make it part of the join condition.
SELECT categories. * , COUNT(categoryID) AS kritCount
FROM categories AS categories
LEFT JOIN krits
ON categories.id = categoryID
AND krits.approved = '1'
GROUP BY categories.id
Try this:
SELECT categories. * , COUNT(categoryID) AS kritCount FROM categories AS categories
LEFT JOIN krits ON categories.id = categoryID
WHERE (krits.approved = '1' OR krits.approved IS NULL)
GROUP BY categories.id
From reading the query, it looks like you want call Categories, and for each category, you want a count of Krits (approved) for the respective categories, and when there are none, you still want the Category, but show the count of 0...
Try this
select
cat.*,
COALESCE( kc.KritCount, 0 ) as KritCount
from
Categories cat
left join ( select k.CategoryID,
count(*) KritCount
from
Krits k
where
k.approved = '1'
group by
k.CategoryID ) kc
on cat.id = kc.CategoryID

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