How do I properly combine these SELECTS (MySQL)? - mysql

I want to get all my posts data to show it on my webpage with this SELECT. I have my table posts that contains most of the posts (+ replies) data and a table social that tracks who views and likes it(each like is a new row).
Normally I can get the username, post time, content... but I'm struggling to get the number of views the post gets, the number of likes, and the number of replies in the same SELECT. My base SELECT looks like this:
SELECT posts.username, posts.time, cat.cat_name,
posts.title, posts.content, posts.reply,
posts.user_file, posts.audio, social.id,
social.views, social.likes
FROM posts
LEFT JOIN user on posts.user_id = user.id
LEFT JOIN cat ON posts.cat_id = cat.id
LEFT JOIN social ON posts.id = social.post_id
If I wanted to get the number of comments per post I would use
(if the value inside reply is 0 it's a post if it's a reply it contains the post id it's referring to):
SELECT COUNT(*) AS `comments` FROM posts GROUP BY reply
/* this returns an error: SQL Error (1242): Subquery returns more than 1 row */
And I would get the number of likes and views like this:
SELECT MAX(social.views) AS views FROM social GROUP BY post_id
SELECT social.likes FROM social WHERE social.id = (SELECT MAX(social.id) FROM social GROUP BY post_id
But if I use it together in the earlier SELECT it just fills every row with the same number. Example:
... posts.audio, social.id, (SELECT MAX(social.views) AS views FROM social GROUP BY post_id) FROM posts ...
This just fills every row even if it shouldn't have views with 25 (correct value for 1 specific row but wrong for everything else).
What would be a proper way of making a bigger SELECT like this?
Not sure if it matters but I am using it with a MySQL module in NodeJS.

Try this way
SELECT posts.username, posts.time, cat.cat_name,
posts.title, posts.content, posts.reply,
posts.user_file, posts.audio,
COUNT(social.views), COUNT(social.likes)
FROM posts
LEFT JOIN user on posts.user_id = user.id
LEFT JOIN cat ON posts.cat_id = cat.id
LEFT JOIN social ON posts.id = social.post_id
GROUP BY
posts.username, posts.time, cat.cat_name,
posts.title, posts.content, posts.reply,
posts.user_file, posts.audio

This is the closest I've gotten to SELECTing everything in one query but I still don't have a way of counting the number of comments:
SELECT posts.username, posts.time, cat.cat_name,
posts.title, posts.content, posts.reply,
posts.user_file, posts.audio,
social.views, social.likes
FROM posts
LEFT JOIN user on posts.user_id = user.id
LEFT JOIN cat ON posts.cat_id = cat.id
LEFT JOIN social ON posts.id = social.post_id
WHERE social.likes IN (SELECT social.likes FROM social
WHERE social.id IN (SELECT MAX(social.id)
FROM social GROUP BY post_id))
GROUP BY social.post_id
HAVING posts.reply = 0

Related

Select posts with user info and media

I need to do the following:
Select the latest posts (last 10 for example) in post table
Join the user info from users table
Join the media avatar for user in media table where type = avatar and media_author = user.id
Join the media of the post (from 1 to 4 for every single post)
in addiction next I need to filter post of friends and post with preferred hashtag of the user that retrieve the data. I think that all this cannot be done in one single query, right?
In the final solution I need to:
get the post
score and order the posts based on some rule (is friends? have hashtag? etc..)
serve the ordered post with user info and posts info (like count, etc..)
Maybe the better solution is:
only select the post, and for each post do two query to retrieve user and media data?
For now this is the queries that I use for the single post:
SELECT posts.id, posts.user_id, posts.privacy, posts.type, posts.published, posts.language,
posts.content, posts.date, posts.entity_map, users.username, media.media_url
FROM posts
LEFT JOIN (SELECT * FROM users) users
ON (users.id = posts.user_id )
LEFT JOIN (SELECT * FROM media ORDER BY media.id DESC) media
ON (media.media_author = posts.user_id AND media.media_type = 'avatar')
WHERE posts.id = ?
SELECT * FROM media WHERE media_parent = ?
Have sense to do 20 query to retrieve a list of 10 posts?
here the tables structure:
tbl posts
id
content
date
entity_map
language
privacy
published
raw_content
type
user_id
tbl users
id
name
username
tbl media
id
media_author
media_date
media_parent
media_status
media_type
media_url
tbl like/love
te be defined, but I think a table with:
id
id_from
id_to
type (like/love/other)
This will get you the latest 10 posts.
SELECT posts.id, posts.user_id, posts.privacy, posts.type, posts.published, posts.language,
posts.content, posts.date, posts.entity_map, users.username, media.media_url
FROM posts
LEFT JOIN (SELECT * FROM users) users
ON (users.id = posts.user_id )
LEFT JOIN (SELECT * FROM media ORDER BY media.id DESC) media
ON (media.media_author = posts.user_id AND media.media_type = 'avatar')
Order By post.date desc
limit 10
To fetch the media you have to make query for each post. As, sql doesn't facilitate the reference like mongodb.

Can you use Count(*) in an inner join?

Currently I have the following database:
Table 1: Customer_Stores
unique_id
page_address
date_added
guide_summary
user_name
cover_photo
guide_title
Table 2: Customer_Stories_Likes
story_id
likex
The 'like' column in the second table contains a 1 or a 0 to indict whether or not a user has liked a post.
What I'd like to do is join these two tables together with 'post_id' and count all of the 'likes' for all the posts based on post_id and order these by how many likes each post got. Is this possible with a single statement? or is it better to use a Count(*) to first determine how many likes each post has?
Yes, it's possible, but you don't need an inner join, because you don't actually need the posts table to do it.
SELECT post_id, count(like) AS post_likes
FROM likes
WHERE like = 1
GROUP BY post_id
ORDER BY post_likes DESC
If you need other information from the posts table as well, you could join it to a subquery that gets the like counts.
SELECT posts.*, like_count
FROM
posts LEFT JOIN
(SELECT post_id, count(like) AS like_count
FROM likes
WHERE like = 1
GROUP BY post_id) AS post_likes
ON posts.post_id = post_likes.post_id
ORDER BY like_count DESC
I used LEFT JOIN rather than INNER JOIN, you can use INNER JOIN if you don't want to include posts with no likes.

SQL query optimization and sort by other row if first is empty

SQL Query:
SELECT
T.*,
U.nick AS author_nick,
P.id AS post_id,
P.name AS post_name,
P.author AS post_author_id,
U2.nick AS post_author
FROM
zero_topics T
LEFT JOIN
zero_posts P
ON
T.id = P.topic_id
LEFT JOIN
zero_players U
ON
T.author = U.uuid
LEFT JOIN
zero_players U2
ON
P.author = U2.uuid
ORDER BY
P.id DESC
Questions:
I need to double left join to get user nick from UUID for topic and post
Not all topics will have post, as you see i sort from post id(it will be date) but it shows on first place topics with last post, and on bottom topics without replies, how can i define order when posts doesn't exists?
1.You will need to double left join if you need to show the nicks in different columns
2.You could use a case in you order by
ORDER BY
CASE
WHEN P.id is null THEN T.ID
ELSE P.ID
END ASC
Final Query:-
SELECT
T.*,
U.nick AS author_nick,
P.id AS post_id,
P.name AS post_name,
P.author AS post_author_id,
U2.nick AS post_author
FROM
zero_topics T
LEFT JOIN
zero_posts P
ON
T.id = P.topic_id
LEFT JOIN
zero_players U
ON
T.author = U.uuid
LEFT JOIN
zero_players U2
ON
P.author = U2.uuid
ORDER BY
CASE
WHEN P.id is null THEN T.ID
ELSE P.ID
END ASC
You actually have two join chains from the topics table. One chain ties an author directly to the topic and one ties an author to each post about the topic, either one or both may be left joined. But once you start a left join in a chain, it must then be continued down the rest of the chain or you nullify the left join. Actually, the topic author is in a chain of length 1 so you don't have to worry about that one.
If every topic has an author, you don't need to left join the first players table (T.author = U.uuid) as that would always link. You would left join down the post chain to see topics even if they have no posts written on them.
Assuming that is what you want to see, then the order by clause could well stay just as you wrote it. What you would get is a list of posts, ordered by ID, with the topics scattered around however they ended up. Any topics with no posts would be clumped all either at the beginning or at the end of the result set, depending on your settings and the DBMS.
If, however, you wrote the order by like this:
order by t.Title, p.id;
Then you would get all the topic ordered by title, with the posts written about that topic ordered by ID within each topic. Any topic with no posts would have a single row (assuming only one topic author) in the proper title order but showing only topic data.
So it all depends on what you want to see.

Mysql forum - get number of replies

I'm making a very simple forum with one table called forum_posts. I store both the replies and the posts in that same table because I've found that works pretty well for comments system I made before.
If the post is a reply, it has a reply_id that is the post_id of the post it is replying to. If it is a 'root post' so to speak, it has a 0 for the reply_id.
I already have the number of views. But I'd like to get the number of replies for each record in the results.
How would I do that?
SELECT a.account_id, a.store_name, p.post_id, p.post_title, p.post_text, p.views, p.creation_timestamp, p.update_timestamp
FROM forum_posts AS p
INNER JOIN accounts AS a
ON p.account_id = a.account_id
WHERE p.reply_id > 0
As you can probably guess, I'm making the forum listings where people choose a forum post to go and view.
You will need to join posts against itself and count it to get the number of replies. This isn't particularly efficient over millions of forum posts - most forums denormalize this and maintain a post count attribute separately.
However, in keeping with what you have, something like (untested)...
SELECT a.account_id, a.store_name, cp.* FROM
(
SELECT p.post_id, p.post_title, p.post_text, p.views, p.creation_timestamp,
p.update_timestamp, p.account_id, (COUNT(*) - 1) as replies
FROM forum_posts AS p
LEFT JOIN forum_posts AS p1 ON p1.reply_id > 0 AND p1.reply_id = p.post_id
GROUP BY p.post_id
)
AS cp
INNER JOIN accounts AS a ON cp.account_id = a.account_id
WHERE cp.replies > 0
The end WHERE is optional. I just copied it from your first query. It's also worth noting that this is MySQL specific as it uses the GROUP BY without a full list of non-aggregated columns (but you tagged your question as MySQL so no problem).
SELECT account_id, store_name, post_id, post_title, post_text, views, creation_timestamp, update_timestamp, IF(reply_id IS NOT NULL, replies, 0)
FROM (
SELECT a.account_id, a.store_name, p.post_id, p.post_title, p.post_text, p.views, p.creation_timestamp, p.update_timestamp, r.reply_id, COUNT(*) as replies
FROM forum_posts AS p
INNER JOIN accounts AS a
ON p.account_id = a.account_id
LEFT JOIN forum_posts AS r
ON p.post_id = r.reply_id
WHERE p.reply_id = 0
GROUP BY p.post_id
) AS sq
It also seems that you need p.reply_id = 0 if you want to show only "root" posts (forum threads) rather than replies.
Outer query makes sure that the posts with no replies (which are still returned in the inner query) are printed with the number of replies 0 rather than 1 (which would be incorrect obviously).

How to left join multiple one to many tables in mysql?

i have a problem with joining three tables in mysql.
lets say we have a table named posts which I keep my entries in it, i have a table named likes which i store user_id's and post_id's in and a third table named comments which i store user_id's and post_id's and comment's text in it.
I need a query that fetches list of my entries, with number of likes and comments for each entry.
Im using this query:
SELECT posts.id, count(comments.id) as total_comments, count(likes.id) as total_likes
FROM `posts`
LEFT OUTER JOIN comments ON comments.post_id = posts.id
LEFT OUTER JOIN likes ON likes.post_id = posts.id
GROUP BY posts.id
but there is a problem with this query, if comments are empty for an item, likes count is just ok, but lets say if an entry has 2 comments and 4 likes, both total_comments and total_likes will be "8", meaning that mysql multiplies them.
I'm confused and I dont know what whould I do.
Thanks in advace.
Use count(distinct comments.id) and count(distinct likes.id), provided these ids are unique.
Well this is one way to approach it (assuming mysql allows derived tables):
SELECT posts.id, comments.total_comments, likes.total_likes
FROM `posts`
LEFT OUTER JOIN (select post_id, count(id) as total_comments from comments) comments
ON comments.post_id = posts.id
LEFT OUTER JOIN (select post_id, count(id) as total_likes from likes) likes
ON likes.post_id = posts.id
You could also use correlated subqueries. You may want a case statment inthere to account for putting in a 0 when there are no matched records.
Let's try a correlated subquery:
SELECT posts.id,
(select count(Id) from comments where post_id = posts.id) as total_comments,
(select count(Id) from likes where post_id = posts.id) as total_likes
FROM `posts`