Get Last conversation row from MySQL database table - mysql

I have a database in MYSQL and it has chat table which looks like this.
I am using this query for fetching these records
SELECT * FROM (
SELECT * FROM `user_chats`
WHERE sender_id =2 OR receiver_id =2
ORDER BY id DESC
) AS tbl
GROUP BY sender_id, receiver_id
But my requirement is only 5,4 ID's records. basically my requirement id fetching last conversation in between 2 users. Here in between 2 & 3 user conversation has 2 records and we want only last one of them i.e. id = 5, here don't need id = 2.
So how we can write a query for that result?

SELECT
*
FROM
user_chats uc
WHERE
not exists (
SELECT
1
FROM
user_chats uc2
WHERE
uc2.created > uc.created AND
(
(uc.sender_id = uc2.sender_id AND uc.reciever_id = uc2.reciever_id) OR
(uc.sender_id = uc2.reciever_id AND uc.reciever_id = uc2.sender_id)
)
)

The following gets you latest record (assuming that the bigger id, the later it was created) meeting your criteria:
SELECT * FROM `user_chats`
WHERE (`sender_id` =2 AND `receiver_id` =3) OR (`sender_id` =3 AND `receiver_id` =2)
ORDER BY `id` DESC
LIMIT 1
which would be a good idea, if id is primary key and it rises along with rising value of created. Otherwise (if you are not sure that id rises when created rises) replace ORDER BY line with the following:
ORDER BY `created` DESC
Plus, in both cases, put proper indexes on: id (if it is your primary key, then there is no need to put additional index on it), sender_id and receiver_id (preferably composite index, meaning the single index for both columns), created (if you want to use ORDER BY created DESC instead of ORDER BY id DESC - otherwise there is no need for that).

try GROUP BY LEAST(sender_id, receiver_id), GREATEST(sender_id, receiver_id)

Related

MySQL : Group By Clause Not Using Index when used with Case

Im using MySQL
I cant change the DB structure, so thats not an option sadly
THE ISSUE:
When i use GROUP BY with CASE (as need in my situation), MYSQL uses
file_sort and the delay is humongous (approx 2-3minutes):
http://sqlfiddle.com/#!9/f97d8/11/0
But when i dont use CASE just GROUP BY group_id , MYSQL easily uses
index and result is fast:
http://sqlfiddle.com/#!9/f97d8/12/0
Scenerio: DETAILED
Table msgs, containing records of sent messages, with fields:
id,
user_id, (the guy who sent the message)
type, (0=> means it's group msg. All the msgs sent under this are marked by group_id. So lets say group_id = 5 sent 5 msgs, the table will have 5 records with group_id =5 and type=0. For type>0, the group_id will be NULL, coz all other types have no group_id as they are individual msgs sent to single recipient)
group_id (if type=0, will contain group_id, else NULL)
Table contains approx 10 million records for user id 50001 and with different types (i.e group as well as individual msgs)
Now the QUERY:
SELECT
msgs.*
FROM
msgs
INNER JOIN accounts
ON (
msgs.user_id = accounts.id
)
WHERE 1
AND msgs.user_id IN (50111)
AND msgs.type IN (0, 1, 5, 7)
GROUP BY CASE `msgs`.`type` WHEN 0 THEN `msgs`.`group_id` ELSE `msgs`.`id` END
ORDER BY `msgs`.`group_id` DESC
LIMIT 100
I HAVE to get summary in a single QUERY,
so msgs sent to group lets say 5 (have 5 records in this table) will be shown as 1 record for summary (i may show COUNT later, but thats not an issue).
The individual msgs have NULL as group_id, so i cant just put 'GROUP BY group_id ' coz that will Group all individual msgs to single record which is not acceptable.
Sample output can be something like:
id owner_id, type group_id COUNT
1 50001 0 2 5
1 50001 1 NULL 1
1 50001 4 NULL 1
1 50001 0 7 5
1 50001 5 NULL 1
1 50001 5 NULL 1
1 50001 5 NULL 1
1 50001 0 10 5
Now the problem is that the GROUP condition after using CASE (which i currently think that i have to because i only need to group by group_id if type=0) is causing alot of delay coz it's not using indexes which it does if i dont use CASE (like just group by group_id ). Please view SQLFiddles above to see the explain results
Can anyone plz give an advice how to get it optimized
UPDATE
I tried a workaround , that does somehow works out (drops INITIAL queries to 1sec). Using union, what it does is, to minimize the resultset by union that forces SQL to write on disk for filesort (due to huge resultset), limit the resultset of group msgs, and individual msgs (view query below)
-- first part of union retrieves group msgs (that have type 0 and needs to be grouped by group_id). Applies the limit to captivate the out of control result set
-- The second query retrieves individual msgs, (those with type !=0, grouped by msgs.id - not necessary but just to be save from duplicate entries due to joins). Applies the limit to captivate the out of control result set
-- JOins the two to retrieve the desired resultset
Here's the query:
SELECT
*
FROM
(
(
SELECT
msgs.id as reference_id, user_id, type, group_id
FROM
msgs
INNER JOIN accounts
ON (msgs.user_id = accounts.id)
WHERE 1
AND accounts.id IN (50111 ) AND type = 0
GROUP BY msgs.group_id
ORDER BY msgs.id DESC
LIMIT 40
)
UNION
ALL
(
SELECT
msgs.id as reference_id, user_id, type, group_id
FROM
msgs
INNER JOIN accounts
ON (
msgs.user_id = accounts.id
)
WHERE 1
AND msgs.type != 0
AND accounts.id IN (50111)
GROUP BY msgs.id
ORDER BY msgs.id
LIMIT 40
)
) AS temp
ORDER BY reference_id
LIMIT 20,20
But has alot of caveats,
-I need to handle the limit in inner queries as well. Lets say 20recs per page, and im on page 4. For inner queries , i need to apply limit 0,80, since im not sure which of the two parts had how many records in the previous 3 pages. So, as the records per page and number of pages grow, my query grows heavier. Lets say 1k rec per page, and im on page 100 , or 1K, the load gets heavier and time exponentially increases
I need to handle ordering in inner queries and then apply on the resultset prepared by union , conditions need to be applied on both inner queries seperately(but not much of an issue)
-Cant use calc_found_rows, so will need to get count using queries seperately
The main issue is the first one. The higher i go with the pagination , the heavier it gets
Would this run faster?
SELECT id, user_id, type, group_id
FROM
( SELECT id, user_id, type, group_id, IFNULL(group_id, id) AS foo
FROM msgs
WHERE user_id IN (50111)
AND type IN (0, 1, 5, 7)
)
GROUP BY foo
ORDER BY `group_id` DESC
LIMIT 100
It needs INDEX(user_id, type).
Does this give the 'correct' answer?
SELECT DISTINCT *
FROM msgs
WHERE user_id IN (50111)
AND type IN (0, 1, 5, 7)
GROUP BY IFNULL(group_id, id)
ORDER BY `group_id` DESC
LIMIT 100
(It needs the same index)

How do I get all the lastest rows from a table based on the contents of two columns?

messages table
id (int)
recipient_id (int)
sender_id (int)
content (text)
created (datetime)
What I'm trying to do is get the latest message from every conversation that a user has had with other users.
Let's say i'm trying to find them for user_id = 3, this is what I have so far. It's not a great solution and feels a bit like a hack but it is working.
SELECT *
FROM messages
WHERE id IN
(SELECT MAX(id) FROM messages
WHERE `recipient_id` = 3 OR `sender_id` = 3
GROUP BY (recipient_id + sender_id))
ORDER BY created DESC
Use GROUP BY recipient_id, sender_id.
SELECT *
FROM messages
WHERE id IN
(SELECT MAX(id) FROM messages
WHERE `recipient_id` = 3 OR `sender_id` = 3
GROUP BY recipient_id, sender_id)
ORDER BY created DESC
let me clear first your question ?
you need latest message from every conversation ?
after see your query i suggest you use this
SELECT * FROM messages WHERE `recipient_id` = 3 OR `sender_id` = 3
GROUP BY (recipient_id + sender_id)) ORDER BY created DESC limit 1
it give you latest message
the reason why i give limit 1 based on your question ? Latest but if you need all from latest message till first just remove limit 1 from that query

Can this MySQL query be more efficient?

UPDATE Here's a sqlfiddle http://sqlfiddle.com/#!2/e0822/1/0
I have a MySQL database of apps (itunes_id), each app id has a comments field. To preserve a history, every time a comment is changed, a new row of data is added. In the query below, I just want a list of the latest entry (highest id) of every app (itunes_id).
Here are the headers of my db:
id (key and auto increment)
itunes_id
comments
date
This query is getting the latest entry for a given itunes_id. How can I make this query more efficient?
SELECT * FROM (
SELECT * FROM (
SELECT * FROM Apps
ORDER BY id DESC
) AS apps1
GROUP BY itunes_id
) AS apps2
LIMIT 0 , 25
This query uses a subquery which separately gets the maximum ID for every itunes_ID. The result of the subquery is then join back on the original table provided that it matches on two columns: itunes_ID and ID.
SELECT a.*
FROM Apps a
INNER JOIN
(
SELECT itunes_id, MAX(ID) max_id
FROM Apps
GROUP BY itunes_id
) b ON a.itunes_id = b.itunes_id AND
a.ID = b.max_ID
LIMIT 0, 25
For faster performance, create a compound column INDEX on columns itunes_ID and ID. EG,
ALTER TABLE Apps ADD INDEX (itunes_ID, ID)
For a similar approach, I use a "recent" boolean field to mark records containing the latest version. This requires an UPDATE query on every insert (deactivate the previous recent record), but allows for a quick select query. Alternatively, you could maintain two tables, one with the recent records, the other one with the history for each app.
EDIT: Maybe you can try a table similar to this:
id int not null auto_increment primary key
version int not null
main_id int null
recent boolean not null
app varchar(32) not null
comment varchar(200) null
You can use the column "main_id" to point to the record with version 1.
SELECT * FROM (
SELECT * FROM (
SELECT * FROM Apps
ORDER BY id DESC
) AS apps1
GROUP BY itunes_id
) AS apps2
LIMIT 0 , 25
will not select the oldest record (you cannot assume the generated key will always be the "oldest"). What you want is something like this:
SELECT * FROM (
SELECT * FROM (
SELECT * FROM Apps
where some_date = (select max(some_date) from Apps limit 1)
ORDER BY id DESC
) AS apps1
GROUP BY itunes_id
) AS apps2
LIMIT 0 , 25
I just want the latest entry (highest id) for a given app (itunes_id)
This will do it
SELECT MAX(id), comments FROM Apps WHERE itunes_id = "iid";
or
SELECT id, comments FROM Apps WHERE itunes_id = "iid" ORDER BY id DESC LIMIT 1;
Where iid is the itunes id for which you want the latest comment.
Make sure id and itunes_id are indexed in a composite index for maximum efficiency.

Group by user and show latest in MYSQL not working

I have a social network I am coding but it's a bit different than others, in this case there is only ever one status show per user on a feed section.
So I need to sort the status by date with the latest ones on top but also group by the userID
unfortunately I can't seem to get this working....
This is my current query:
SELECT *
FROM status
WHERE userID != "83" #This is just so my statuses don't show
GROUP BY userID
ORDER BY addedDate DESC
LIMIT 10
I expect to see the latest status results and only one per user instead I see the first statuses so the group by is working but not the order by.
Thanks in advance.
As mentioned in the comments to Robin's answer, that approach is unreliable because MySQL does not guarantee that it will always return the most recent status from each group. You must instead join your table with a subquery that selects the most recent status (based on addedDate).
SELECT *
FROM status
NATURAL JOIN (
SELECT userID, MAX(addedDate) as addedDate
FROM status
GROUP BY userID
) AS mostRecent
ORDER BY addedDate DESC
LIMIT 10
Note that if a user has multiple status updates with the same addedDate, the server will return all of them (whereas Robin's query would return an indeterminate one); if you need control over such a situation, you will need to define how one determines which such status update should be selected.
SELECT userID, max(addedDate)
FROM status
WHERE userID != "83" #This is just so my statuses don't show
GROUP BY userID
SELECT *
FROM ( SELECT *
FROM status
WHERE userID != "83"
ORDER BY addedDate DESC) AS h
GROUP BY userID
ORDER BY addedDate DESC
LIMIT 10
You must ORDER BY before GROUP BY'ing.
Example 1
Example 2

MySQL Select Statement - GROUP BY/Unique

I'm currently using PHP/MySQL to select some data from my database. My current SELECT statement is:
mysql_query("SELECT * FROM tblpm WHERE receiver_id ='$usrID' GROUP BY
thread_id ORDER BY unique_id DESC") or die(mysql_error());
There are some columns that have the same "thread-id", thus, I am using the GROUP BY, so that only one "thread-id" column is selected. Everything works fine with the grouping, however, it is selecting the first 'thread-id' column, and not the most recent (which is what I want).
For example, there are 4 columns all with the same thread_id:
thread_id unique_id
222 1
222 2
222 3
222 4
When I do the GROUP BY, I would like it to retrieve the one with unique id 4 (the most recently created column), not unique id 1 (the oldest column created).
Any ideas on how to achieve this with my SELECT statement? Thanks.
SELECT thread_id, MAX(unique_id)
FROM tblpm
GROUP by thread_id
so:
mysql_query(<<<END
SELECT thread_id, MAX(unique_id)
FROM tblpm
WHERE receiver_id ='$usrID'
GROUP by thread_id
END
) or die(mysql_error());
and of course make sure you escape $usrID if it comes from the browser.
SELECT DISTINCT thread_id, unique_id
FROM tblpm
WHERE receiver_id = '$usrID'
GROUP BY thread_id
ORDER BY unique_id DESC;