Cannot get last item from table with mysql group by - mysql

I am trying to make a mysql query that receives the last item in a group by clause. However, when I group by, it gives me the first record. I tried many joins, and many searches, but cannot find the right way to go about it.
My database is designed like so:
messages:
message_id *
thread_id
user_id
message
created_at
users:
user_id*
name
threads:
thread_id*
name
usersthreads:
id*
user_id
thread_id
The query I am trying to make should be, provided a user_id,:
threads.thread_id
users.name
messages.message
messages.created_at
The catch here is this, for users.name, I need not the provided user's name, but the other user's name that is in the thread. My current stab at it right now stands as this:
select t.thread_id as thread, t.name as name, messages.message as message, max(messages.created_at) as date
from (SELECT threads.thread_id, nt.name
FROM threads,messages,
(SELECT users.name as name, usersthreads.thread_id as threadID
from users,usersthreads
where users.user_id=usersthreads.user_id
and users.user_id!='$userID') as nt
WHERE threads.thread_id=messages.thread_id
and nt.threadID=threads.thread_id
AND messages.user_id=$userID
GROUP BY messages.thread_id) as t
inner join messages
on t.thread_id = messages.thread_id
group by t.thread_id
order by date DESC
And the problem with this query is that it returns what I need, but the message that is returned per each thread is the first message of the thread, not the last ("most recent") message. Thank you.

Related

SQL query with most recent name and total count

I already have a table, "table_one", set up on phpMyAdmin that has the following columns:
USER_ID: A discord user ID (message.author.id)
USER_NAME: A discord username (message.author.name)
USER_NICKNAME: The user's display name on the server (message.author.display_name)
TIMESTAMP: A datetime timestamp when the message was entered (message.created_at)
MESSAGE CONTENT: A cleaned input keyword to successful completion of content, just for this example consider "apple" or "orange" as the two target keywords.
What I'd like as a result is a view or query that returns a table with the following:
The user's most recent display name (USER_NICKNAME), based off the most recent timestamp
The total number of times a user has entered a specific keyword. Such as confining the search to only include "apple" but not instances "orange"
My intention is that if a user entered a keyword 10 times, then changed their server nickname and entered the same keyword 10 more times, the result would show their most recent nickname and that they entered the keyword 20 times in total.
This is the closest I have gotten to my desired result so far. The query correctly groups instances where user has changed their nickname based on the static discord ID, but I would like it to retain this functionality while instead showing the most recent USER_NICKNAME instead of a USER_ID:
SELECT USER_ID, COUNT(USER_ID)
FROM table_one
WHERE MESSAGE_CONTENT = 'apple'
GROUP BY USER_ID
I don't think there is an uncomplicated way to do this. In Postgres, I would use the SELECT DISTINCT ON to get the nickname, but in MySQL I believe you are limited to JOINing grouped queries.
I would combine two queries (or three, depending how you look at it).
First, to get the keyword count, use your original query:
SELECT USER_ID, COUNT(USER_ID) as apple_count
FROM table_one
WHERE MESSAGE_CONTENT = 'apple'
GROUP BY USER_ID;
Second, to get the last nickname, group by USER_ID without subsetting rows and use the result as a subquery in a JOIN statement:
SELECT a.USER_ID, a.USER_NICKNAME AS last_nickname
FROM table_one a
INNER JOIN
(SELECT USER_ID, MAX(TIMESTAMP) AS max_ts
FROM table_one
GROUP BY USER_ID) b
ON a.USER_ID = b.USER_ID AND TIMESTAMP = max_ts
I would then JOIN these two, using a WITH statement to increase the clarity of what's going on:
WITH
nicknames AS
(SELECT a.USER_ID, a.USER_NICKNAME AS last_nickname
FROM table_one a
INNER JOIN
(SELECT USER_ID, MAX(TIMESTAMP) AS max_ts
FROM table_one
GROUP BY USER_ID) b
ON a.USER_ID = b.USER_ID AND TIMESTAMP = max_ts),
counts AS
(SELECT USER_ID, COUNT(USER_ID) AS apple_count
FROM table_one
WHERE MESSAGE_CONTENT = 'apple'
GROUP BY USER_ID)
SELECT nicknames.USER_ID, nicknames.last_nickname, counts.apple_count
FROM nicknames
INNER JOIN counts
ON nicknames.USER_ID = counts.USER_ID;

Do I need to repeat the WHERE clause in a 'IN (SELECT MAX(id)' subquery?

Do I need to specify the WHERE clause inside this subquery as well?
SELECT title, message FROM messages WHERE sender = '.$myid.' AND read != 1 AND id
IN (SELECT MAX(id) FROM messages GROUP BY conversation) ORDER BY id DESC
It seems like the result is the same with/without repeating the WHERE clause, but it's hard to test without having a lot of different users and messages. So I need to be more sure.
Is it necessary to repeat the WHERE clause like this 🡫, or is the IN subquery already narrowed down by the first WHERE clause? (I assume the latter but I'm not sure).
SELECT title, message FROM messages WHERE sender = '.$myid.' AND read != 1 AND id
IN (SELECT MAX(id) FROM messages WHERE sender = '.$myid.' AND read != 1 GROUP BY conversation) ORDER BY id DESC
Both are simplified examples from my code. I hope my question makes sense.
The where in the outer query is redundant as in your inner query (subquery) you are requesting specific ids using the constraint you want so basically when you have something like this:
SELECT title, message FROM messages WHERE id IN (SELECT MAX(id) FROM messages WHERE
sender = '.$myid.' AND read != 1 GROUP BY conversation) ORDER BY id DESC
it would be like you have gathered the ids first & then requested the specific records similar to: select x from y where id in(1, 2, 3)
For this same exact query it would be like running the following 2 queries:
Query 1: SELECT MAX(id) FROM messages WHERE sender = '.$myid.' AND read != 1 GROUP BY conversation
Result: 1, 2, 3
The second query is:
Query 2: SELECT title, message FROM messages WHERE id IN (1, 2, 3) ORDER BY id DESC
The sub query select is completely separate to the first in terms of arguments parsed, so you will have to enter the where clause information again

Group by gives error "Invalid use of group function"

I'd like to get the number of likes each user got from the following tables:
Users table: contains the userid, email, contact no
Like table: which contains picture ids, and userids who liked it
Picture posted table*: contains picture ids, user id who posted it
I am using the following query which is giving the error "Invalid use of group function":
select sum(count(pid)) from p_like where pid in (
select pid from p_picuser where userid in (
SELECT userid from p_users
)
) GROUP BY pid
What am I doing wrong?
You can't aggregate (sums, counts, etc) the same variables on which you're grouping by. If you'd like counts by user, group on that. So maybe try something like:
SELECT userid, count(*) from p_like GROUP BY user
to get the like-count's by userId from your p_like table. Strictly-speaking, this your answer.
To add more user details then, you can make that a sub-query and join to you p_user table, e.g.
SELECT email, like_count from p_users pusers, (
SELECT userid, count(*) like_cound from p_like GROUP BY user
) group_sub
WHERE p_users.userid = group_sub.userid

MySQL Group By single result order by time

I am trying to fetch users from messages table which has send message to me but need only one result per sender which is max of creation column which is time of message creation
SELECT messages.from, account_images.profile, bio.user_full_name
FROM messages
INNER JOIN account_images ON account_images.uuid=messages.from
INNER JOIN bio ON bio.uuid=messages.from
WHERE messages.to='me'
GROUP BY messages.from
ORDER BY messages.creation DESC
A user whose message is created recently must be on top but it not coming on top using this code. I referred php mysql Group By to get latest record, not first record but not getting anything
Any help?
You could have written the current query as...
SELECT DISTINCT messages.from, account_images.profile, bio.user_full_name
FROM messages
INNER JOIN account_images ON account_images.uuid=messages.from
INNER JOIN bio ON bio.uuid=messages.from
WHERE messages.to='me'
Which would have been marginally more efficient - but as you will have noticed with your current query, it doesn't get sorted by the most recent message.
This is sorted as you request:
SELECT messages.from, account_images.profile, bio.user_full_name
FROM messages
INNER JOIN account_images ON account_images.uuid=messages.from
INNER JOIN bio ON bio.uuid=messages.from
WHERE messages.to='me'
GROUP BY messages.from
ORDER BY MAX(messages.creation) DESC;
It is possible to get the result you desire using DISTINCT instead of aggregation, but it's neither efficient nor elegant.
I found this mysql doc very helpful in cases like this: http://dev.mysql.com/doc/refman/5.0/en/example-maximum-row.html
SELECT *
FROM messages m
LEFT JOIN messages m2 ON m.creation > m2.creation
WHERE m2.creation IS NULL
AND m1.to='me'

MySQL having trouble sorting a query correctly

This is for a custom forum I am working on. i need to select all threads, get the number of posts in each thread and also the last post in each thread. I can get the number of posts, but my query is returning the first post rather than the last.
SELECT thread_id, thread_title, p.*, COUNT(p.post_id) AS Posts
FROM forums_threads
JOIN forums_posts AS p ON post_thread_id=thread_id
WHERE thread_forum_id=84
GROUP BY thread_id
ORDER BY thread_date DESC, post_date DESC
As #mixman suggests, you need to link to forum_posts twice: once to get the aggregate amounts (post count and maximum post date), and once to get the actual content of that last post (and I'm assuming that by "last" you mean "most recent") in the thread:
SELECT ft.thread_id, ft.thread_title, fp.*, pmin.postcount
FROM forums_threads AS ft
JOIN (
SELECT post_thread_id, MAX(post_date), COUNT(post_id) AS postcount
FROM forums_posts
GROUP BY post_thread_id
) AS pmin ON ft.thread_id=pmin.post_thread_id
JOIN forums_posts AS fp ON fp.post_thread_id=pmin.post_thread_id AND fp.post_date = pmin.post_date
WHERE ft.thread_forum_id=84
ORDER BY ft.thread_date DESC
Grouping happens before ordering as the SQL suggests. Ordering is then done against the whole result, returning the first thread_date for a grouped thread_id. Restructuring of the SQL with subqueries/self-joins should get the job done.