MySQL LEFT JOIN LIMIT for each row [duplicate] - mysql

This question already has answers here:
Using LIMIT within GROUP BY to get N results per group?
(14 answers)
Closed 2 years ago.
I would like to get a list of Posts including the last 2 comments for each particular post (if any).
I have been using the query below, that returns all the comments for each post, however it has a limitation, because the GROUP_CONCAT() length is limited to 1024 characters by default, so if there are a lot of comments on a post, the 'comments' value will be cut off, and it won't be a valid JSON output anymore.
SELECT post.*,
CONCAT('[', GROUP_CONCAT(DISTINCT JSON_OBJECT(
'id', postComment.id,
'date', postComment.date,
'content', postComment.content,
'user', CONCAT(postComment.fName, postComment.lName)
) ORDER BY postComment.id DESC),']') AS comments
FROM posts AS post
LEFT JOIN (
SELECT comment.*, commentUser.fName, commentUser.lName
FROM comments AS comment
LEFT JOIN users AS commentUser ON comment.uID = commentUser.id
ORDER BY comment.id DESC
) AS postComment ON postComment.postID = post.id
WHERE post.uID = 37
GROUP BY post.id
ORDER BY post.id DESC
LIMIT 0,5;
Due to this limitation, I was thinking of returning only the last 2 comments for each post by adding LIMIT 0,2 to the LEFT JOIN SELECT clause like this:
SELECT post.*,
CONCAT('[', GROUP_CONCAT(DISTINCT JSON_OBJECT(
'id', postComment.id,
'date', postComment.date,
'content', postComment.content,
'user', CONCAT(postComment.fName, postComment.lName)
) ORDER BY postComment.id DESC),']') AS comments
FROM posts AS post
LEFT JOIN (
SELECT comment.*, commentUser.fName, commentUser.lName
FROM comments AS comment
LEFT JOIN users AS commentUser ON comment.uID = commentUser.id
ORDER BY comment.id DESC
LIMIT 0, 2
) AS postComment ON postComment.postID = post.id
WHERE post.uID = 37
GROUP BY post.id
ORDER BY post.id DESC
LIMIT 0,5;
But now it returns the first two comment for the very first post only...
Could anyone tell me how can I get this work correctly, so the query results return the first two comments FOR EACH particular post and why this is happening?
Cheers!

As a starter: if your actual problem is the limitation on the number of characters returned by GROUP_CONCAT(), you can increase it by modifying session variable group_concat_max_len, like so:
SET SESSION group_concat_max_len = 1000000;
Then you can run your query (in the same session).
As for your question itself: I would recommend turning the LEFT JOIN to a correlated subquery; basically the subquery is re-executed for each and every post, so you can use ORDER BY and LIMIT:
SELECT p.*,
CONCAT(
'[',
(
SELECT GROUP_CONCAT(
JSON_OBJECT(
'id', c.id,
'date', c.date,
'content', c.content,
'user', CONCAT(u.fName, u.lName)
)
ORDER BY c.id DESC
)
FROM comments AS c
LEFT JOIN users AS u ON c.uID = u.id
WHERE c.postID = p.id
ORDER BY c.date DESC
LIMIT 2
),
']'
) AS comments
FROM posts AS p
WHERE p.uID = 37
GROUP BY p.id
ORDER BY p.id DESC
LIMIT 0,5;
As a final thought: do upgrade to MySQL 5.7.22 or higher! Then you can use JSON_ARRAYAGG(), and you won't have to worry about this all anymore.

Related

Query returning false PHP

I am having trouble with a MySQL query. The query is as follows:
SET #catlocation = (SELECT id FROM categories WHERE url_name='hexcode');
SELECT
subs.display_name AS display,
subs.url_name AS url,
(
SELECT title
FROM threads
WHERE location = subs.id
ORDER BY time_submitted DESC
LIMIT 1
) AS title,
(
SELECT username
FROM users
WHERE uid = (
SELECT uid
FROM threads
WHERE location = subs.id
ORDER BY time_submitted DESC
LIMIT 1
)
LIMIT 1
) AS author,
(
SELECT COUNT(*)
FROM threads
WHERE location = subs.id
ORDER BY time_submitted DESC
LIMIT 1
) AS thread_count
FROM (
SELECT *
FROM categories
WHERE parent_id = #catlocation
) AS subs
When I try to run this through PHP I get a false result and an error of:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT subs.display_name AS display, subs.url_name AS url, ( SELECT threads.' at line 7
I have no idea what the syntax error could be, if someone could point it out to me that would be wonderful.
EDIT: Could this be caused by having two select statements (The one that sets #catlocation and the main query?)
You can refactor your request with joins to increase performance.
SELECT s.display_name display, s.url_name url,
t1.title, u.username author,
COUNT(t2.title) total
FROM categories s
LEFT JOIN threads t1 ON t1.id = (SELECT id FROM threads
WHERE location = s.id
ORDER BY time_submitted DESC
LIMIT 1)
LEFT JOIN users u ON u.uid = t1.uid
LEFT JOIN threads t2 ON t2.location = s.id
WHERE s.parent_id = #catlocation
GROUP BY s.display_name, s.url_name, t1.title, u.username
In a ansi SQL you need to declare a tag for each table or omit it if there is only one. Try taking out the "threads." everywhere, it is not needed
It appears the first SELECT statement which set #catlocation was causing the problem. I moved it into a subquery and the query executed successfully
The new query is as follows:
SELECT categories.display_name display,
categories.url_name url,
threads.title title,
users.username author,
( SELECT COUNT(title)
FROM threads
WHERE location = categories.id
) total
FROM categories
LEFT JOIN threads
ON threads.tid = ( SELECT tid
FROM `threads`
WHERE location = categories.id
ORDER BY time_submitted DESC
LIMIT 1 )
LEFT JOIN users ON users.uid = threads.uid
WHERE categories.parent_id = ( SELECT id
FROM `categories`
WHERE url_name='hexcode'
LIMIT 1 );
I will continue to refactor the query by using JOINs (once I learn how to use them). Thanks to all that suggested fixes, I didn't understand the JOIN answer and still couldn't get it to run without error.

mysql: subquery with returned many row

I have this query for load user stream in my app , is it too hard if we have 10000 matched row in 'follow' ?
SELECT *
FROM post
WHERE user_id
IN (SELECT follow_id
FROM follow
WHERE id='$some_id')
AND type='accepted'
ORDER BY id DESC LIMIT $page , 20
Syntactically your code looks correct.. I don't see any errors so then if you're talking about efficiency I would join the tables and include the second filter on the JOIN
SELECT p.*
FROM post p
JOIN follow f
ON f.follow_id = p.user_id
AND f.id = '$some_id'
WHERE p.type = 'accepted'
ORDER BY p.id DESC LIMIT $page , 20
MySQL handles large sets of data a lot better through a join than with an IN()...
Think of it this way.. because the IN() can have pretty much anything inside of it, MySQL has to check it with everything returned for each row... instead of checking once when you JOIN..
With that many returning, I have a feeling a Join might be more efficient
SELECT *
FROM post p Join
follow f On p.user_id = f.follow_id
WHERE f.id='$some_id'
AND p.type='accepted'
ORDER BY p.id DESC LIMIT $page , 20

Sort phpbb posts. Original post first, rest descending by date. Some mysql trials

Here's the end result of my latest attempt that throws an error (based on answers I've seen here regarding mysql questions)
SELECT result.* FROM ( SELECT p.post_id FROM phpbb_posts p WHERE p.topic_id = 297 limit 1 ORDER BY p.post_time ASC UNION SELECT p.post_id FROM phpbb_posts p WHERE p.topic_id = 297 ORDER BY p.post_time DESC ) result ORDER BY result.id LIMIT 10
Basically I'm trying to grab the first post (order by p.post_time asc limit 1), then append to that a descending order of posts in the topic, to show the latest post first.
There's no support for doing this that I can find at phpbb's forums (yes they will say you can set it in control panel, but that just makes the original post appear at the end of the topic).
Actual code of viewtopic.php for this query (at least I believe it's the correct place to make changes):
$sql = 'SELECT p.post_id
FROM ' . POSTS_TABLE . ' p' . (($join_user_sql[$sort_key]) ? ', ' . USERS_TABLE . ' u': '') . "
WHERE p.topic_id = $topic_id
" . ((!$auth->acl_get('m_approve', $forum_id)) ? 'AND p.post_approved = 1' : '') . "
" . (($join_user_sql[$sort_key]) ? 'AND u.user_id = p.poster_id': '') . "
$limit_posts_time
ORDER BY $sql_sort_order";
$result = $db->sql_query_limit($sql, $sql_limit, $sql_start);
I've been at this a while. I'm not lazy, at least not tonight, I'm just tired and out of my depth.
Thanks.
from your first query it looks like you're trying to get the earliest/original post then append 9 latest post order by post_time desc. so then maybe a query like this (sqlFiddle)
SELECT post_id,timeorder FROM
(SELECT p.post_id,now()as timeorder FROM phpbb_posts p WHERE p.topic_id = 297 ORDER BY p.post_time ASC LIMIT 1)T1
UNION
SELECT post_id,timeorder FROM
(SELECT p.post_id,p.post_time as timeorder FROM phpbb_posts p WHERE p.topic_id = 297
AND EXISTS(SELECT 1 FROM phpbb_posts p2 WHERE p2.post_time < p.post_time AND p2.topic_id = 297)
ORDER BY p.post_time DESC LIMIT 9)T2
ORDER BY timeorder DESC
the timeorder in the query is just used for sorting at the end so that the original post is first row and then the rest of the rows are latest posts order by post_time desc
the EXISTS in T2 check is to make sure you don't include the original post(since it's already selected by T1)

Limit concatenatings in group_concat

I'm just encountering an issue that i have no clue on how to solve. It is related to this (solved) problem.
Like mentioned in the other post, i have a Media table that can hold up many records of the same user, but i only want to display a maximum of six records (in order, In any case, only one Type 1 image, followed by a maximum of five Type 2 images).
The query i now have works fine as long as there is only one Type 1 image, but when i add another Type 1 image, the query displays them both. Unfortunatly, something like ORDER BY UserMedia.m_Type = 1 DESC LIMIT 1 in an GROUP_CONCAT doesn't work, but it is exactly what i need. Anybody a clever idea how to realise this?
I have a SQL Fiddle here with the relevant code. My query looks like this
SELECT
User.u_UserName, User.u_UserMail, User.u_UserRegistration,
Status.us_PaymentStatus,
Sex.us_Gender, Sex.us_Interest,
Personal.up_Name, Personal.up_Dob, Personal.up_City, Personal.up_Province,
UserMedia.m_Id, UserMedia.m_Type, SUBSTRING_INDEX(
GROUP_CONCAT(
CONCAT(
UserMedia.m_Type, ':', UserMedia.m_File
)
ORDER BY UserMedia.m_Type = 1, UserMedia.m_Date DESC SEPARATOR '|'
),'|',6
) AS userFiles
FROM User AS User
JOIN User_Status AS Status ON Status.User_u_UserId = User.u_UserId
JOIN User_Sex_Info AS Sex ON Sex.User_u_UserId = User.u_UserId
LEFT JOIN User_Personal_Info AS Personal ON Personal.User_u_UserId = User.u_UserId
LEFT JOIN Media AS UserMedia ON UserMedia.User_u_UserId = User.u_UserId
WHERE User.u_UserId = '18'
GROUP BY User.u_UserId
Went for a walk and came with the following solution. Maybe not the most beautiful one, but at least it works. I also realised i didn't need the CONCAT function
SELECT
User.u_UserName, User.u_UserMail, User.u_UserRegistration,
Status.us_PaymentStatus,
Sex.us_Gender, Sex.us_Interest,
Personal.up_Name, Personal.up_Dob, Personal.up_City, Personal.up_Province,
UserMedia.m_Id, UserMedia.m_Type, SUBSTRING_INDEX(
GROUP_CONCAT(
UserMedia.m_File
ORDER BY UserMedia.m_Type = 1 DESC
SEPARATOR '|'
),'|',1
) AS userFiles,
SUBSTRING_INDEX(
GROUP_CONCAT(
UserMedia.m_File
ORDER BY UserMedia.m_Date DESC
SEPARATOR '|'
),'|',5
) AS userTypes,
SUBSTRING_INDEX(
GROUP_CONCAT(
Interests.ui_Interest SEPARATOR '|'
),'|',5
) AS userInterests
FROM User AS User
JOIN User_Status AS Status ON Status.User_u_UserId = User.u_UserId
JOIN User_Sex_Info AS Sex ON Sex.User_u_UserId = User.u_UserId
LEFT JOIN User_Personal_Info AS Personal ON Personal.User_u_UserId = User.u_UserId
LEFT JOIN Media AS UserMedia ON UserMedia.User_u_UserId = User.u_UserId
LEFT JOIN User_Interest_Info AS Interests ON Interests.User_u_UserId = User.u_UserId
WHERE User.u_UserId = :uId
GROUP BY User.u_UserId

MySQL - very complicated random row

I've got my tables posts and user_unread_posts.
In posts, all the posts of a forum are saved, and in user_unread_posts all posts are saved which are read by a user.
user_undread_postslooks like this:
id uid pid
Now I want to allow users to open a random post which they haven't read. I've tried something like
SELECT * FROM posts
LEFT JOIN user_unread_posts uup
ON uup.pid=posts.id
WHERE uup.uid<>1
ORDER BY RAND()
LIMIT 1
(Whilst 1 is a placeholder UID)
But it doesn't work, like it should work, it return posts too, which are read... How can I fix that?
SELECT *
FROM posts
WHERE id NOT IN
(
SELECT pid
FROM user_unread_posts uup
WHERE uid = $myuserid
)
ORDER BY
RAND()
LIMIT 1
You wanted to use IS NULL with the LEFT JOIN. Using <> turns the LEFT JOIN into an INNER JOIN because NULL can never match the <> operator.
Here is a corrected query:
SELECT * FROM posts
LEFT JOIN user_unread_posts uup
ON uup.pid=posts.id
WHERE uup.uid IS NULL
ORDER BY RAND()
LIMIT 1