SQL GROUP BY selecting - mysql

Here is my code:
mysql_query("SELECT * FROM messages WHERE to_user_id = '$user_id' GROUP BY from_user_id ORDER BY read,sent_date DESC")
and here is my table structure
I use the GROUPY BY from_user_id statement to briefly show a list of "conversations" instead of every single message. Like this
But, as you can see in the image, the top two are the wrong way round, the first one says "1 week ago" and the one below says "2 days ago". The reason for these being in the wrong order is due to the GROUP BY from_user_id statement. Because it groups all the messages from that user and it wont have the the most recent time on it.
So my question is:
How can I GROUP BY from_user_id by the most recent record?

You can not SELECT columns not listed in the GROUP BY or otherwise allowed functions. This is how GROUP BY works.
Although most other SQL flavors would fail on such a query, MySQL doesn't. Instead it provides arbitrary results - what you are seeing.
You can solve this a few different ways. For example:
SELECT the ids of the latest user conversations using GROUP BY in a separate query.
Do it all in one query by sub-selecting the ids or JOIN the set of ids.

Since MySQL doesn't support windowing functions like ROW_NUMBER() OVER() you can do something like this:
SELECT *
FROM Messages
where id in (SELECT MAX(ID) FROM messages GROUP BY from_user_id)
ORDER BY sent_date, read
The subquery will only return the newest message id for each user. I'm assuming your auto_increment corresponds with the order the messages are sent in. Even if it's not the exact logic you might want, this is a general technique to get a specific subset of values from grouped records that works in MySQL.

Try with this
SELECT *,MAX(id) as OrderField FROM messages WHERE to_user_id = '$user_id'
GROUP BY from_user_id
ORDER BY OrderField DESC,read

Related

Need help creating 1 MySQL query

I'm creating a message board. When a topic gets a reply, the entire topic (all rows that has the topicid) must be bumped to the top of the forum. Pinned topics should always be displayed first, then followed by the topic that has the most recent post date because they are bumped to the top when they get a reply.
This is a pic of the table that contains the posts.
DB Table before query
I need 1 query that will do the following:
group all the topicid together (lets call this "group");
within each group sort the rows by parentid ascending, but NULL always is sorted first;
groups that are "pinned" are displayed first
then groups that have the "latest" post displayed first
The query should give the following results
Results of the query
I think this query will do what you want.
SELECT *
FROM messages m
ORDER BY IF(pinned='yes','9999-12-31 23:59:59', (SELECT MAX(date) FROM messages m2 WHERE m2.topicid = m.topicid)) DESC,
topicid, IFNULL(parentid, 0)
The first part of the order by ensures that groups that are pinned are ordered first, followed by groups that have the most recent post. It does this by selecting the maximum possible date when the group is pinned, otherwise the latest date for posts in that group and sorts by that value descending. The second part then sorts those posts by topicid, and the final part sorts by parentid. To ensure that posts with a NULL parentid sort first, we use an IFNULL clause on parentid to set the sorting value to 0 when parentid is NULL.
I've created an SQLFiddle to demonstrate this.
Edit This updated query will also sort pinned topics by latest date instead of just by topicid. It does this by adding 1000 years to the date of pinned posts, thus ensuring that pinned posts sort ahead of non-pinned posts while retaining the ordering between pinned posts as well.
SELECT *
FROM messages m
ORDER BY (SELECT MAX(IF(pinned='yes', date + interval 1000 year, date))
FROM messages m2
WHERE m2.topicid = m.topicid) DESC,
topicid, ifnull(parentid, 0)
Here's an updated SQLFiddle to demonstrate.
I'm afraid I can only answer parts of your question, but I'm giving it a shot and posting it anyway in the hope that it will help you solve your issue.
First, I'd grab the pinned messages (lastest first in this query):
SELECT * FROM messages WHERE pinned = 1 ORDER BY date DESC
Then, you can grab - let's say - the 10 most recent topics:
SELECT MAX(date) AS latest, topicid FROM messages GROUP BY topicid ORDER BY latest DESC LIMIT 10
Now you should know the topicids from which you want to display messages. You could "chain together" the messages with a join:
SELECT * FROM messages AS m1 JOIN messages AS m2 ON m1.postid = m2.parentid WHERE m1.topicid IN <some stuff here> ORDER BY m1.topicid ASC, m1.postid DESC
Sorry for the incomplete answer. Any comments to help me fill the gaps are welcome ;)

SQL: Group By is mismatching records

I'm trying to get the highest version within a group. My query:
SELECT
rubric_id,
max(version) as version,
group_id
FROM
rubrics
WHERE
client_id = 1
GROUP BY
group_id
The Data:
The Results:
The rubric of ID 2 does not have a version of 2, why is this being mismatched? What do I need to do to correct this?
Edit, not a duplicate:
This is not a duplicate of SQL Select only rows with Max Value on a Column , which is a post I have read and referenced before writing this. My question is not how to find the max, my question is why is the version not matched to the correct ID
MySQL is confusing you by letting you get away with having a column in your select that isn't in your group by. To resolve the issue, make sure you don't select any field that isn't in the group by.
Instead of trying to get everything in one statement, you will need to use a subquery to find the max_version_id and then join to it.
SELECT T.*
FROM rubrics T
JOIN
(
SELECT
group_id,
max(version) as max_version
FROM
rubrics
GROUP BY
group_id
) dedupe
on T.group_id = dedupe.group_id
and T.version_id = dedupe.max_version_id
WHERE
T.client_id = 1
Edit: So MySQL allows it, but I don't think it's a good practise to use it.
You are trying to query non-aggregated data from an aggregated query. You should not do that.
A GROUP BY takes the field it should make group of rows with (in your case, what you say with your GROUP BY is: give me a result per different group_id) and gives a result (the aggregated data) based on the grouping.
Here, you try to access non aggregated data (rubric_id in your case). For some reason, the query does not crash and picks a "random" id in your aggregated data.

grouping and ordering conversation messages with users

This title is awful, but can't think of a better one. This is twitter specific, but can apply to any similar application where you have private conversations.
I want to display all direct messages a user might have. All messages from/to a specific user should be grouped together.
Here's a SQL Fiddle example.
In this example, I received messages from two users, and replied to one (type of in/out). The first query is simply ordering by date descending. The order is 3 2 1, but what I really want is 3 1 2 because 3 it's the latest, and 1 is related to 3.
The second query gets the correct order, but using GROUP_CONCAT. This makes for very messy query, and I would rather have one message in each row so I don't have to split them in PHP.
The third query was added because I'm not yet sure if I want to order each conversation ASC or DESC. This one does the GROUP_CONCAT ASC correctly, but because the message from user2 is newer than the first message from user1, the two rows are reversed.
Hope this makes sense. The two orders I would like to achieve are 3 1 2 and 1 3 2, each message as its own row, and two different queries. Thanks!
Are you looking for this?
SELECT *
FROM test
ORDER BY "user", date DESC;
EDIT:
You want to order users by the time of their most recent message. That requires something like a join:
select t.*
from test t join
(select user, max(date) as maxdate
from test
group by user
) tu
on t.user = tu.user
order by tu.maxdate desc, user, date desc;

Passing query result into subquery

SELECT alert,
(select created_at from alerts
WHERE alert = #ALERT ORDER BY created_at desc LIMIT 1)
AS latest FROM alerts GROUP BY alert;
I am having an issue with the above query where I would like to pass in each alert into the subquery so that I have a column called latest which displays the latest alert for each group of alerts. How should I do this?
This is called a correlated subquery. To make it work, you need table aliases:
SELECT a1.alert,
(select a2.created_at
from alerts a2
WHERE a2.alert = a1.alert
ORDER BY a2.created_at desc
LIMIT 1
) AS latest
FROM alerts a1
GROUP BY a1.alert;
Table aliases are a good habit to get into, because they often make the SQL more readable. It is also a good idea to use table aliases with column references, so you easily know where the column is coming from.
EDIT:
If you really want the latest, you can get it by simply doing:
select alert, max(created_at)
from alerts
group by alert;
If you are trying to get the latest created_at date for each group of alerts, there is a simpler way.
SELECT
alert,
max (created_at) AS latest
FROM
alerts
GROUP BY
alert;
I would do the following
SELECT
alert_group_name,
MAX(created_at) AS latest
FROM
alerts A
GROUP BY
alert_group_name;
For a correlated subquery, you need to reference an expression from the outer query.
The best way to do that is to assign an alias to the table on the outer query, and then reference that in the inner query. Best practice is to assign an alias to EVERY table reference, and qualify EVERY column reference.
All that needs to be done to "fix" your query is to replace the reference to "#ALERT" with a reference to the alert column from the table on the outer query.
In our shop, that statement would be formatted something like this:
SELECT a.alert
, (SELECT l.created_at
FROM alerts l
WHERE l.alert = a.alert
ORDER BY l.created_at DESC
LIMIT 1
) AS latest
FROM alerts a
GROUP
BY a.alert
Not because that's easier to write that way, but more importantly it's easier to read and understand what the statement is doing.
The correlated subquery approach can be efficient for a small number of rows returned (a very restrictive WHERE clause on the outermost query.) But in general, correlated subqueries in the SELECT list can make for a (what we refer to in our shop) an LDQ "light dimming query".
In our shop, if we needed the resultset returned by that query, that statement would likely be rewritten as:
SELECT a.alert
, MAX(a.created_at) AS latest
FROM alerts a
GROUP
BY a.alert
And we'd definitely have an index defined ON alerts(alert,created_at) (or an index with additional columns after those first two.)
size, we
(I don't anticipate any cases where this statement would return a different result.)

how to structure a mysql subquery

I have a users table, and an appointments table. For any given day, I would like a query that selects
1) the user_id the appointment is scheduled with
2) the number of appointments for that user for the specified day.
It seems I can one or the other, but I'm unsure of how to do it with one query. For instance, I can do:
SELECT user_id FROM appt_tbl WHERE DATE(appt_date_time) = '2012-10-14'
group by user_id;
Which will give me the users that have an appointment that day, but how can I add to this query another column that will give me how many appointments each user has? Assuming I need some kind of subquery, but I'm unsure of how to structure that.
SQL uses the notion of "aggregate functions" to get you this information. You can use them with any aggregating query (i.e. it has "group by" in it).
SELECT user_id, count(*) as num_apts ...
Try adding COUNT(*) to your query:
SELECT user_id, COUNT(*) FROM appt_tbl WHERE DATE(appt_date_time) = '2012-10-14'
group by user_id;