Social Networking posts comment algorithm for mysql - mysql

$query = $this->db->query("SELECT a.fullname AS fullname_post , a.picture, a.user_id, c.post_id, c.date_posted, c.total_comments, c.content AS post, UNIX_TIMESTAMP() - c.date_posted AS p_time_spent, c.photo, d.comment_id, d.comment_date, d.content AS comments, UNIX_TIMESTAMP() - d.comment_date AS c_time_spent, d.post_id_fk, e.fullname AS fullname_comments
from $post_table c
LEFT JOIN $comments_table d ON d.post_id_fk = c.post_id
LEFT JOIN $user_table a on a.user_id=c.post_owner_fk
LEFT JOIN $user_table e on e.user_id=d.comment_owner_id_fk
LEFT JOIN $contact_table f on f.user_id_fk='$user_id' OR f.user_id_fk2='$user_id'
WHERE c.post_owner_fk = '$user_id' OR c.post_owner_fk = f.friend_id_fk OR c.post_owner_fk = f.friend_id_fk2
ORDER BY c.post_id DESC, d.comment_id ASC"
);
The mysql query above works fine if I just want to retrieve all comments to a specific post but I have no idea how to limit the number of comments to display.
I try to put select in one of the left join and put some limit.
LEFT JOIN (SELECT * FROM $comments_table LIMIT 4) d ON d.post_id_fk = c.post_id
but it only display 4 comments and the other posts has no comments displayed even if there are comments in the database. I think it only retrieve 4 comments in the comment table.
So please any idea how to solve this problem thanks!

Can you try something like this in MySQL?
SELECT a.fullname AS fullname_post , a.picture, a.user_id, c.post_id, c.date_posted,
c.total_comments, c.content AS post, UNIX_TIMESTAMP() - c.date_posted AS p_time_spent,
c.photo, d.comment_id, d.comment_date, d.content AS comments,
UNIX_TIMESTAMP() - d.comment_date AS c_time_spent, d.post_id_fk,
e.fullname AS fullname_comments
FROM $post_table c
LEFT JOIN $comments_table d ON d.post_id_fk = c.post_id
LEFT JOIN
(SELECT tmpc.post_id_fk
FROM $comments_table tmpc
WHERE tmpc.post_id_fk = c.post_id
ORDER BY tmpc.comment_date DESC
LIMIT 4) as climited
ON climited.post_id_fk = c.post_id
LEFT JOIN $user_table a on a.user_id=c.post_owner_fk
LEFT JOIN $user_table e on e.user_id=d.comment_owner_id_fk
LEFT JOIN $contact_table f on f.user_id_fk='$user_id'
OR f.user_id_fk2='$user_id'
WHERE c.post_owner_fk = '$user_id'
OR c.post_owner_fk = f.friend_id_fk
OR c.post_owner_fk = f.friend_id_fk2
ORDER BY c.post_id DESC, d.comment_id ASC
Edit:
Replaced the IN clause with another join to a 'limited' SELECT query

Related

select taking a lot of time inside union all ordering by date desc?

I have a select to get alerts.
select 'comments' prefix, c.foto, c.data as data, c.user, concat(k.user, ' comments your post') as logs from comentarios c
inner join posts p on c.foto = p.id
inner join cadastro k on c.user = k.id
where p.user = 1 and p.user <> c.user and c.delete = 0
union all
select 'like' prefix, l.post, l.data as data, l.user, concat(k.user, ' liked your post') as logs from likes l
inner join posts p on l.post = p.id
inner join cadastro k on l.user = k.id
where p.user = 1 and l.user <> p.user
order by data desc
limit 5
The problem is if I run the select this way it will take 2.4871 seconds.
If I remove the select 'like'... = 0.0024, but if I run this select apart it will take only 0.010 without the order by data desc.
any ideas why? I don't know if the problem is the union or the order by...
they are all well indexed.

How to use ORDER BY in a query which is containing JOIN?

I have a query which selects all comments for each post. Here is my query:
SELECT c.id, c.content, u.name, u.reputation, SUM(v.value) AS total_vote_comments
FROM comments c
INNER JOIN users u ON c.user_id = u.id
LEFT JOIN votes_comments v ON c.id = v.comment_id
WHERE c.post_id = :id;
Now I want to add ORDER BY c.id to that query. How?
An order by is irrelevant because this query returns one row:
SELECT c.id, c.content, u.name, u.reputation, SUM(v.value) AS total_vote_comments
FROM comments c INNER JOIN
users u
ON c.user_id = u.id LEFT JOIN
votes_comments v
ON c.id = v.comment_id
WHERE c.post_id = :id;
This is an aggregation query (because of the SUM()) without a GROUP BY. Such a query always returns one row, even when no rows match the join.
You probably want a GROUP BY. My best guess is:
SELECT c.id, c.content, u.name, u.reputation, SUM(v.value) AS total_vote_comments
FROM comments c INNER JOIN
users u
ON c.user_id = u.id LEFT JOIN
votes_comments v
ON c.id = v.comment_id
WHERE c.post_id = :id
GROUP BY c.id, c.content, u.name, u.reputation
ORDER BY c.id;
You can just add the ORDER BY clause at the end:
SELECT c.id, c.content, u.name, u.reputation, SUM(v.value) AS total_vote_comments
FROM comments c
INNER JOIN users u ON c.user_id = u.id
LEFT JOIN votes_comments v ON c.id = v.comment_id
WHERE c.post_id = :id
ORDER BY c.id;
By using this query you get only one row which id you put in where clause.
If you want to get one post ordered then you write order by in last.
If you want to get last's comment first so use desc with order by.
SELECT c.id, c.content, u.name, u.reputation, SUM(v.value) AS total_vote_commentFROM comments c INNER JOIN users u ON c.user_id = u.id LEFT JOIN votes_comments v ON c.id = v.comment_id WHERE c.post_id = :id order by id desc;
desc = descending order
asc = ascending order

Select previous and next records in join statement

On the page for viewing posts I thought it would be a nice idea to have links for the next and previous posts. I thought I should get those 3 records in 1 query since I'm so smart and I had a bunch of time to waste anyway. So as you probably guessed I couldn't do it, but I'm interested to find the solution. Here is what I have
SELECT a.id,
a.title,
a.body,
p.id AS prev_id,
p.title AS prev_title,
n.id AS next_id,
n.title AS next_title
FROM posts a
LEFT JOIN posts p
ON p.id < a.id
LEFT JOIN posts n
ON n.id > a.id
WHERE a.id = ?
LIMIT 1
The problem is that for prev_id and prev_title I always get the first record in the table. I tried adding ORDER BY but it didn't really seem to affect the join. How can I select the previous and not the first record using a join like in the example?
I hope that this one you might find helpful:
SELECT a.id,
a.title,
a.body,
p.id AS prev_id,
p.title AS prev_title,
n.id AS next_id,
n.title AS next_title
FROM posts a
INNER JOIN
(
SELECT
_a.id AS RefID,
MIN(_a.id - _p.id) AS MinDistPrev,
MIN(_n.id - _a.id) AS MinDistNext
FROM
posts _a
LEFT JOIN posts _p
ON _p.id < _a.id
LEFT JOIN posts _n
ON _n.id > _a.id
WHERE
_a.id = ?
GROUP BY
_a.id
) AS _PrevNextDist
ON _PrevNextDist.RefID = a.ID
LEFT JOIN posts p
ON p.id < a.id
AND a.id - p.id = _PrevNextDist.MinDistPrev
LEFT JOIN posts n
ON n.id > a.id
AND n.id - a.id = _PrevNextDist.MinDistNext
LIMIT 1
Additionally, adding ORDER BY p.id DESC to the original code solved the problem as well
SELECT a.id,
a.title,
a.body,
p.id AS prev_id,
p.title AS prev_title,
n.id AS next_id,
n.title AS next_title
FROM posts a
LEFT JOIN posts p
ON p.id < a.id
LEFT JOIN posts n
ON n.id > a.id
WHERE a.id = ?
ORDER BY p.id DESC
LIMIT 1

Rewrite MySQL Query without NESTED SELECT?

I have a MySQL query that is currently using a nested select, and I am wondering if it is possible to rewrite the query to not use a nested select, and if so how?
The query is as follows
SELECT
b.id,
b.name,
b.description,
b.order,
b.icon,
b.locked,
u.username AS lastPoster,
p.time AS lastPostTime,
p1.subject AS lastPostTopicSubject,
p2.postscount AS totalPosts,
t1.topicscount AS totalTopics,
p.subject AS lastPostSubject,
t.id AS lastPostTopicId
FROM kf_boards AS b
LEFT JOIN kf_topics AS t ON (t.boardid = b.id)
LEFT JOIN (SELECT posterid, topicid, time, subject
FROM kf_posts
ORDER BY time DESC) AS p ON (p.topicid = t.id)
LEFT JOIN (SELECT subject
FROM kf_posts
ORDER BY time ASC) AS p1 ON (p.topicid = t.id)
LEFT JOIN (SELECT COUNT(id) AS postscount
FROM kf_posts) AS p2 ON (p.topicid = t.id)
LEFT JOIN (SELECT COUNT(id) AS topicscount
FROM kf_topics) AS t1 ON (t.boardid = b.id)
LEFT JOIN kf_users AS u ON (p.posterid = u.id)
WHERE b.categoryid = :catid
GROUP BY b.name
ORDER BY b.order
And the database structure is as follows
Any help would be much appriciated!
Thanks!
Edit: Tried below query, results returned
Results should be as follows
try:
SELECT
b.id, b.name, b.description, b.order, b.icon, b.locked,
u.username AS lastPoster, p.time AS lastPostTime,
p.subject AS lastPostSubject, t.id AS lastPostTopicId
FROM kf_boards AS b
LEFT JOIN kf_topics AS t ON t.boardid = b.id
LEFT JOIN kf_posts AS p ON p.topicid = t.id
LEFT JOIN kf_users AS u ON p.posterid = u.id
WHERE b.categoryid = :catid
GROUP BY b.name
ORDER BY b.order ASC, p.time DESC
UPDATE: bellow is for your new query.
SELECT b.id, b.name, b.description, b.order, b.icon, b.locked,
u.username AS lastPoster, MAX(p.time) AS lastPostTime,
p.subject AS lastPostTopicSubject, count(p.id) AS totalPosts,
count(t.id) AS totalTopics, p.subject AS lastPostSubject,
max(t.id) AS lastPostTopicId
FROM kf_boards AS b
LEFT JOIN kf_topics AS t ON t.boardid = b.id
LEFT JOIN kf_posts AS p ON p.topicid = t.id
LEFT JOIN kf_users AS u ON p.posterid = u.id
WHERE b.categoryid = :catid
GROUP BY b.name, b.id, b.name, b.description, b.order, b.icon,
b.locked, u.username, p.subject
ORDER BY b.order
Here's a solution that might help, however, I have additional suggestions to simplify all querying later via triggers. I'll explain that later.
I'm starting with the inner-most query on just board IDs for category (your parameter) and that the board HAS POSTINGS (not via LEFT-JOIN). From this, I am getting just the maximum post ID per board regardless of topic (just that it must be of a valid board per the joins).
Once I have that, the next query out re-joins to the posts table based on the last post to determine the topic... then re-joins THAT to the posts again on the same topic ID. With that, I can get the FIRST Post ID and total Entries for this topic for the board... all grouped per "board ID".
These are obviously only boards that HAVE AT LEAST 1 Board, but that's not what you want. You want ALL boards regardless of a post. So, I'm back to the beginning to query kf_boards again with same WHERE on category ID = your parameter... THIS gets you all the boards for the category...
NOW, you can left-join to the pre-aggregate query for min/max post and entries count... Then take THAT to left-join to the posts table again but TWICE... Once for the FIRST post (so you can get the initial subject caption, time and whatever else you might care about), and AGAIN for the LAST POST to get ITs time, subject, etc... You already have the total post entries for this topic from the pre-aggregate query. Finally, a left-join on the last post to the users table to see who posted it last.
I've tested the syntax on it and it works, just can't confirm based on actual data.
SELECT
B.ID,
B.Name,
B.Description,
B.Order,
B.Icon,
FP.Subject as FirstPostSubject,
FP.Time as FirstPostTime,
LP.Subject as LastPostSubject,
LP.Time as LastPostTime,
U.UserName as LastPostUserName,
QryPerBoard.PostEntries
from
kf_boards B
LEFT JOIN
( select
PQ1.ID,
PQ1.LastPostID,
MIN( P2.ID ) as FirstPostID,
COUNT(*) as PostEntries
from
( SELECT
B1.ID,
MAX( P1.ID ) as LastPostID
from
kf_boards B1
join kf_topics T1
ON B1.ID = T1.BoardID
join kf_posts P1
ON T1.ID = P1.TopicID
where
B1.CategoryID = 1 <-- Insert your Category Parameter ID here
group by
B1.ID ) as PQ1
LEFT JOIN kf_posts P1
ON PQ1.LastPostID = P1.ID
LEFT JOIN kf_posts P2
ON P1.TopicID = P2.TopicID
group by
PQ1.ID ) QryPerBoard
ON B.ID = QryPerBoard.ID
LEFT JOIN kf_posts FP
ON QryPerBoard.FirstPostID = FP.ID
LEFT JOIN kf_posts LP
ON QryPerBoard.LastPostID = LP.ID
LEFT JOIN kf_users U
ON LP.PosterID = U.ID
where
B.CategoryID = 1 <-- Insert your Category Parameter ID here (second copy for parameter)
Now, how I would adjust to prevent the recursive level of querying, especially for a website. Use triggers. When a POST is created and saved, have a trigger that does a few things...
REVISED THOUGHT on trigger impact.
Update the kf_Boards with the latest TOPIC ID any post was created for the respective board ID so you never have to look for it later, just take whatever is the last and run with it. In addition, update the TOPIC record. Have a column for the FIRST POST, LAST POST and TOTAL POSTS for the topic. If its the first post to the topic, update both first AND last post with the new ID and increment the total post count.
The time to incorporate these triggers to update the "extra" columns would save such complexities for future queries. You'll basically be able to do something like
select
B.*,
LP.Fields, <obviously apply specific fields you want per table>
FP.Fields,
U.Fields
from
kf_boards B
LEFT JOIN kf_topics T
on B.LastTopicID = T.ID
LEFT JOIN kf_posts FP
on T.FirstPostID = FP.ID
LEFT JOIN kf_posts LP
on T.LastPostID = LP.ID
LEFT JOIN kf_users U
on LP.PosterID = U.ID
where
B.CategoryID = 1 <-- your parameterID
It seems possible to remove all the subqueries,
but the query will be more clear if the last post, and the first post on the corresponding
topic is found using subqueries:
SELECT b.id, b.name, b.description, b.sortorder, b.icon, b.locked,
u.username AS lastPoster,
p1.time AS lastpostTime,
p0.subject AS lastPostTopicSubject,
COUNT(DISTINCT p.id) AS totalPosts,
COUNT(DISTINCT t.id) AS totalTopics,
p1.subject AS lastPostSubject,
p1.topicid AS lastPostTopicId
FROM kf_boards b
LEFT JOIN kf_topics t ON t.boardid = b.id
LEFT JOIN kf_posts p ON p.topicid = t.id
LEFT JOIN kf_posts p1
ON p1.time = (SELECT MAX(time) FROM kf_posts p
INNER JOIN kf_topics t
ON p.topicid = t.id
WHERE t.boardid = b.id)
LEFT JOIN kf_users u ON u.id = p1.posterid
LEFT JOIN kf_posts p0
ON p0.time = (SELECT MIN(time) FROM kf_posts p0
WHERE p0.topicid = p1.topicid)
WHERE b.categoryid = :catid
GROUP BY b.id
ORDER BY b.sortorder;
However, the following query, using self joins to find posts that have no related previous/nest post should give the same answer:
SELECT b.id, b.name, b.description, b.sortorder, b.icon, b.locked,
u.username AS lastPoster,
lastpost.time AS lastpostTime,
firstpost.subject AS lastPostTopicSubject,
COUNT(DISTINCT p.id) AS totalPosts,
COUNT(DISTINCT t.id) AS totalTopics,
lastpost.subject AS lastPostSubject,
lastpost.topicid AS lastPostTopicId
FROM kf_boards b
LEFT JOIN kf_topics t ON t.boardid = b.id
LEFT JOIN kf_posts p ON p.topicid = t.id
LEFT JOIN kf_topics lasttopic ON lasttopic.boardid = b.id
LEFT JOIN kf_posts lastpost ON lastpost.topicid = lasttopic.id
LEFT JOIN kf_topics nexttopic ON nexttopic.boardid = b.id
LEFT JOIN kf_posts nextpost -- order posts
ON nextpost.topicid = nexttopic.id -- in same board
AND nextpost.time > lastpost.time -- by time
LEFT JOIN kf_users u ON u.id = lastpost.posterid
LEFT JOIN kf_posts AS firstpost ON firstpost.topicid = lastpost.topicid
LEFT JOIN kf_posts prevpost -- order posts
ON prevpost.topicid = firstpost.topicid -- on same topic
AND prevpost.time < firstpost.time -- by time
WHERE nextpost.id IS NULL -- last post has no next
AND prevpost.id IS NULL -- first post on topic has no previous
AND b.categoryid = :catid
GROUP BY b.id
ORDER BY b.sortorder;
Check the result at http://sqlfiddle.com/#!2/1c042/1/0

Gathering multiple fields

$q = "SELECT s.id, s.title, s.description,
(SELECT COUNT(*) FROM ".FORUM_THREADS." t WHERE t.cat_id = s.id) AS topics,
(SELECT COUNT(*) FROM ".FORUM_REPLIES." r INNER JOIN ".FORUM_THREADS." t ON r.thread_id = t.id
WHERE t.cat_id = s.id) AS replies,
(SELECT r.date FROM ".FORUM_REPLIES." r INNER JOIN ".FORUM_THREADS." t ON r.thread_id = t.id
WHERE t.cat_id = s.id ORDER BY r.date DESC LIMIT 1) AS last_post
FROM ".FORUM_SUBCATEGORIES." s WHERE s.parent = '$catid' AND s.status = '0' ORDER BY s.id";
I am attempting to select more than one field on the following part of the query
(SELECT r.date FROM ".FORUM_REPLIES." r INNER JOIN ".FORUM_THREADS." t ON r.thread_id = t.id
INNER JOIN ".TBL_USERS." u ON u.id = r.author WHERE t.cat_id = s.id ORDER BY r.date DESC LIMIT 1) AS last_post
Along with r.date, I want to select u.username and r.author.
How can I go about doing this?
Thanks!
Just add them to the SELECT:
(SELECT r.date, r.author, u.username FROM ".FORUM_REPLIES." r INNER JOIN ".FORUM_THREADS." t ON r.thread_id = t.id
INNER JOIN ".TBL_USERS." u ON u.id = r.author WHERE t.cat_id = s.id ORDER BY r.date DESC LIMIT 1) AS last_post
UPDATED after comment from OP:
You need to do 3 separate selects OR (depending on your data model) change the query so that the last_post query ends up after/in the FROM clause (there it can have as many columns as you want)...
Luke, you have a central select statement which uses nested select statements for getting the count. You can't depend on the nested select statements to count as the inner join, so you're going to have to add them to the central select statement instead.
In other words, join ".FORUM_REPLIES." and "u" (not sure what that's supposed to represent) with ".FORUM_SUBCATEGORIES.". I'd write the query for you, but I don't know how to link subcategories with replies and subcategories with u.