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
Related
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.
I am trying to select the count of likes on a specific project. The idea i came up with is
CAST(count(uploads.ID in (SELECT uploadID from votes)) as decimal) as numberoflikes
this works but the query then only returns one thing.
Entire query
SELECT DISTINCT users.NAME AS username
,users.ID AS userID
,subjects.NAME AS subjectname
,uploads.TIME
,uploads.description
,uploads.NAME
,uploads.ID
,CASE
WHEN uploads.ID IN (
SELECT uploadID
FROM votes
WHERE userID = 2
)
THEN CAST(1 AS DECIMAL)
ELSE CAST(0 AS DECIMAL)
END AS liked
,CASE
WHEN uploads.ID IN (
SELECT uploadID
FROM bookmarks
WHERE userID = 2
)
THEN CAST(1 AS DECIMAL)
ELSE CAST(0 AS DECIMAL)
END AS bookmarked
,CAST(count(uploads.ID IN (
SELECT uploadID
FROM votes
)) AS DECIMAL) AS numberoflikes
FROM uploads
INNER JOIN subjects ON (subjects.ID = uploads.subjectID)
INNER JOIN users ON (users.ID = uploads.userID)
INNER JOIN uploadGrades ON (uploads.ID = uploadGrades.uploadID)
INNER JOIN grades ON (grades.ID = uploadGrades.gradeID)
WHERE uploads.active = 1
AND subjects.ID IN (
SELECT subjectID
FROM userSubjects
INNER JOIN users ON (users.ID = userSubjects.userID)
WHERE userSubjects.userID = 2
)
AND grades.ID IN (
SELECT userGrades.gradeID
FROM uploadGrades
INNER JOIN userGrades ON (uploadGrades.gradeID = userGrades.gradeID)
WHERE userGrades.userID = 2
)
ORDER BY uploads.trueRating DESC;
Lets try a reduce version of your query, That is the base to get better answers
I reduce the initial query to user and upload to start. Also remove the fields you already know how to calculate.
.
SELECT DISTINCT users.NAME AS username
,users.ID AS userID
,uploads.NAME
,uploads.ID
,CAST(count(uploads.ID IN (
SELECT uploadID
FROM votes
)) AS DECIMAL) AS numberoflikes
FROM uploads
INNER JOIN users ON (users.ID = uploads.userID)
WHERE uploads.active = 1
ORDER BY uploads.trueRating DESC;
Then add votes with LEFT JOIN to replace the SELECT in the COUNT that way if not match you will get NULL and as I say in my comment COUNT doesnt count NULL's
.
SELECT DISTINCT users.NAME AS username
,users.ID AS userID
,uploads.NAME
,uploads.ID
,CAST(count(votes.uploadID)) AS DECIMAL) AS numberoflikes
FROM uploads
INNER JOIN users ON (users.ID = uploads.userID)
LEFT JOIN votes ON (uploads.ID = votes.uploadID)
WHERE uploads.active = 1
ORDER BY uploads.trueRating DESC;
Try something like this...
SELECT users.name as username, users.ID as userID, subjects.name as subjectname,
uploads.time, uploads.description, uploads.name, uploads.ID,
count(userVotes.userId), count(bookmarksMade.userId),
FROM uploads
join subjects on(subjects.ID = uploads.subjectID)
join users on(users.ID = uploads.userID)
join uploadGrades on(uploads.ID = uploadGrades.uploadID)
join grades on(grades.ID = uploadGrades.gradeID)
left join (select userId, uploadId from votes where userId = 2) as userVotes on uploads.id = userVotes.uploadId
left join (select userId, uploadId from bookmarks where userId = 2) as bookmarksMade on uploads.id = bookmarksMade.uploadId
join userSubjects on subjects.id = userSubjects.subjectID
WHERE uploads.active = 1 AND
userSubjects.userID = 2
ORDER BY uploads.trueRating DESC;
But, I am leaving out the userGrades thing, because you are doing a funky join there that I don't really understand (joining two tables on something that looks like it is not the whole primary key on either table).
Anyway, you really need to go to something more like this or what Oropeza suggests in his answer. Get more direct about what you want. This query looks like a monster that has been growing and getting things added in with "IN" clauses, as you needed them. Time to go back to the drawing board and think about what you want and how to get at it directly.
count(uploads.ID in (SELECT uploadID from votes)) as numberoflikes
group by uploads.Id ORDER BY uploads.trueRating DESC
I managed to do it like this. If i added the group by then it split the numberoflikes into rows and returned more then one row. Thanks for the help!
I have this data in my database(3,15,6,4,15), and I tried to show it on my table using group concat, the problem is i got duplicate results. (3,15,6,4,15,3,15,6,4,15,3,15,6,4,15).
I tried to use distinct but it eliminate also the other "15".
What is the best solution for that?
thanks!
this is my query
SELECT users.*, GROUP_CONCAT(written.score separator ' - ') as Wscore, student_subject.*,SUM(written.score) as total, SUM(written.item) as item FROM users JOIN written ON users.idnumber=written.idnumber JOIN student_subject ON users.idnumber=student_subject.idnumber WHERE student_subject.teacher='$login_session' AND written.section='$section' AND written.level='$level' AND written.year='$year' AND written.subject='$subject' AND users.gender='male' AND written.period='first' GROUP BY users.idnumber order by users.lname
You probably just want distinct in the group_concat():
SELECT u.*, GROUP_CONCAT(distinct w.score separator ' - ') as Wscore,
ss.*, SUM(w.score) as total, SUM(w.item) as item
FROM users u JOIN
written w
ON u.idnumber = w.idnumber JOIN
student_subject ss
ON u.idnumber = ss.idnumber
WHERE ss.teacher = '$login_session' AND w.section='$section' AND
w.level = '$level' AND w.year = '$year' AND w.subject = '$subject' AND
u.gender = 'male' AND w.period = 'first'
GROUP BY u.idnumber
order by u.lname;
Notice how table aliases make the query easier to write and to read.
I have written an sql statement that besides all the other columns should return the number of comments and the number of likes of a certain post. It works perfectly when I don't try to get the number of times it has been shared too. When I try to get the number of time it was shared instead it returns a wrong number of like that seems to be either the number of shares and likes or something like that. Here is the code:
SELECT
[...],
count(CS.commentId) as shares,
count(CL.commentId) as numberOfLikes
FROM
(SELECT *
FROM accountSpecifics
WHERE institutionId= '{$keyword['id']}') `AS`
INNER JOIN
account A ON A.id = `AS`.accountId
INNER JOIN
comment C ON C.accountId = A.id
LEFT JOIN
commentLikes CL ON C.commentId = CL.commentId
LEFT JOIN
commentShares CS ON C.commentId = CS.commentId
GROUP BY
C.time
ORDER BY
year, month, hour, month
Could you also tell me if you think this is an efficient SQL statement or if you would do it differently? thank you!
Do this instead:
SELECT
[...],
(select count(*) from commentLikes CL where C.commentId = CL.commentId) as shares,
(select count(*) from commentShares CS where C.commentId = CS.commentId) as numberOfLikes
FROM
(SELECT *
FROM accountSpecifics
WHERE institutionId= '{$keyword['id']}') `AS`
INNER JOIN account A ON A.id = `AS`.accountId
INNER JOIN comment C ON C.accountId = A.id
GROUP BY C.time
ORDER BY year, month, hour, month
If you use JOINs, you're getting back one result set, and COUNT(any field) simply counts the rows and will always compute the same thing, and in this case the wrong thing. Subqueries are what you need here. Good luck!
EDIT: as posted below, count(distinct something) can also work, but it's making the database do more work than necessary for the answer you want to end up with.
Quick fix:
SELECT
[...],
count(DISTINCT CS.commentId) as shares,
count(DISTINCT CL.commentId) as numberOfLikes
Better approach:
SELECT [...]
, Coalesce(shares.numberOfShares, 0) As numberOfShares
, Coalesce(likes.numberOfLikes , 0) As numberOfLikes
FROM [...]
LEFT
JOIN (
SELECT commentId
, Count(*) As numberOfShares
FROM commentShares
GROUP
BY commentId
) As shares
ON shares.commentId = c.commentId
LEFT
JOIN (
SELECT commentId
, Count(*) As numberOfLikes
FROM commentLikes
GROUP
BY commentId
) As likes
ON likes.commentId = c.commentId
I have a contract table like this
And I want the result to look like this
What query should I use to retrieve the result like the example? I was trying to use group query but it still doesn't do what I want.
Something like this will return the specified result. It's not clear what columns you want to "match" on, I used the first four columns, because those are identical on the rows you show "grouped" together.
The "trick" is to use a GROUP BY to get the distinct list of rows you want to return. You can the use outer joins to pick out the individual rows, to match to the rows returned from the inline view (aliased as g).
This query assumes that Contract column will be unique within each "group". That is, there won't be two rows, for example, with Contract='I' for ('ENG','SWD','ABCDf','2012-11-06'). (In the sample data, it is unique, but we haven't been given any guarantee that it is unique.)
SELECT g.departemen
, g.section
, g.name
, g.start
, i.`end contract` AS `End Contract I`
, p.`end contract` AS `End Contract P`
, q.`end contract` AS `End Contract II`
FROM ( SELECT t.departemen
, t.section
, t.name
, t.start
FROM mytable t
GROUP
BY t.departemen
, t.section
, t.name
, t.start
) g
LEFT
JOIN mytable i
ON i.departemen = g.departemen
AND i.section = g.section
AND i.name = g.name
AND i.start = g.start
AND i.contract = 'I'
LEFT
JOIN mytable p
ON p.departemen = g.departemen
AND p.section = g.section
AND p.name = g.name
AND p.start = g.start
AND p.contract = 'P'
LEFT
JOIN mytable q
ON q.departemen = g.departemen
AND q.section = g.section
AND q.name = g.name
AND q.start = g.start
AND q.contract = 'II'