How to get last record for group? - mysql

I have a table called tbl_chat and tbl_post.
The tbl_chat is given as follows.
|--------------------------------------------------------------------------------|
| chat_id | message | from_user | to_user | post_id |send_date |
|--------------------------------------------------------------------------------|
| 1 | Hi | 23 | A | 35 | 2016-04-01 17:35|
| 2 | Test | 24 | A | 35 | 2016-04-02 01:35|
| 3 | Thut | A | 23 | 35 | 2016-04-02 03:35|
| 4 | test | A | 24 | 35 | 2016-04-02 12:35|
| 5 | Hi | 23 | A | 35 | 2016-04-03 17:35|
|--------------------------------------------------------------------------------|
Now, in the chat table we can see three users are interacting with each other. The Admin (A), user with id = 23 and user = 24.
So there is basically two chat thread.
One between A and 23
Another between A and 24.
I want a query which will show the two chat threads, with the last chat message. Just like in case of facebook chat list showing all the chat-threads with mentioning the last chat.
I am writing a query like this.
SELECT * FROM tbl_chat, tbl_post
WHERE tbl_post.post_id = tbl_chat.post_id
AND tbl_post.post_id = '39'
GROUP BY tbl_chat.chat_from
ORDER BY date DESC
The query has a problem. It is first retrieving all the chats and grouping it w.r.t. chat_from and then ORDERING it Descending-wise.
So first it's creating the group, and then ordering the group.
Also, the first query produces three group, taking the reply message from Admin as a separate group. Since GROUP BY chat_from.
How can I solve this issue?
EDIT:-
I would be grateful if someone can build the query in Active-Records of Codeigniter.

DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(chat_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,message VARCHAR(20) NOT NULL
,from_user VARCHAR(12)
,to_user VARCHAR(12)
,post_id INT NOT NULL
,send_date DATETIME NOT NULL
);
INSERT INTO my_table VALUES
(1,'Hi' ,'23','A' ,35,'2016-04-01 17:35:00'),
(2,'Test','24','A' ,35,'2016-04-02 01:35:00'),
(3,'Thut','A' ,'23',35,'2016-04-02 03:35:00'),
(4,'test','A' ,'24',35,'2016-04-02 12:35:00'),
(5,'Hi' ,'23','A' ,35,'2016-04-03 17:35:00');
SELECT a.*
FROM my_table a
JOIN
( SELECT LEAST(from_user,to_user) user1
, GREATEST(from_user,to_user) user2
, MAX(send_date) send_date
FROM my_table
GROUP
BY user1
, user2
) b
ON b.user1 = LEAST(a.from_user,a.to_user)
AND b.user2 = GREATEST(a.from_user,a.to_user)
AND b.send_date = a.send_date;
+---------+---------+-----------+---------+---------+---------------------+
| chat_id | message | from_user | to_user | post_id | send_date |
+---------+---------+-----------+---------+---------+---------------------+
| 4 | test | A | 24 | 35 | 2016-04-02 12:35:00 |
| 5 | Hi | 23 | A | 35 | 2016-04-03 17:35:00 |
+---------+---------+-----------+---------+---------+---------------------+

You can use NOT EXISTS() :
SELECT * FROM tbl_chat
INNER JOIN tbl_post
ON tbl_post.post_id = tbl_chat.post_id
WHERE NOT EXISTS(SELECT 1 FROM tbl_chat s
WHERE tbl_chat.from_user IN(s.from_user,s.to_user)
AND tbl_chat.to_user IN(s.from_user,s.to_user)
AND tbl_chat.date < s.date)
Although date field looks like DATE type, which is curious - how would you find the difference between two messages at the same day?

Related

How to retrive a list of the most recent entry in a table of each 'player'

This is similar to the table I have:
+-----------+--------+
| Player id | Score |
+-----------+--------+
| User 1 | 10 |
| User 2 | 22 |
| User 1 | 17 |
| User 3 | 34 |
| User 4 | 23 |
| User 2 | 12 |
| User 3 | 10 |
| User 4 | 30 |
+-----------+--------+
I also have a timestamp on each row.
What is the best way to get the most recent row for each 'player'. like the following result:
+-----------+--------+
| Player id | Score |
+-----------+--------+
| User 1 | 10 |
| User 2 | 22 |
| User 3 | 34 |
| User 4 | 23 |
+-----------+--------+
Try this query
SELECT Player_id,
SUBSTRING_INDEX(GROUP_CONCAT(score ORDER BY date_added DESC),',',1) AS recent_score,
SUBSTRING_INDEX(GROUP_CONCAT(date_added ORDER BY date_added DESC),',',1) AS recent_date
FROM table
GROUP BY Player_id
Here I have assumed your timestamp field as date_added, and also score by recent date
I think you can use a query like this:
(time field is your timestamp field that elaborate recent record)
SELECT
`t1`.`Player id`, `t1`.`Score`, `t1`.`time field`
FROM `yourTable` As `t1`
JOIN
`yourTable` As `t2`
ON `t1`.`Player id` = `t2`.`Player id`
AND `t1`.`time field` <= `t2`.`time field`
GROUP BY
`t1`.`Player id`, `t1`.`Score`, `t1`.`time field`
HAVING
COUNT(`t2`.`Player id`) = 1;
[SQL Fiddle Demo]
I think this can help you:
SELECT
<Player_id>, <Score>
FROM
<your_table>
WHERE
(<clientID>,<timestamp>) IN (
SELECT
<Player_id>, MAX(<timestamp>)
FROM
<your_table>
GROUP BY
<Player_id>
);
Just replace the <enclosed> part with the corresponding column name of your table.

Populate sql column from query result

I have 2 tables
requests
requests_votes
request structure is:
id | req_name | content | hits
------------------------------
1 | Sample | Some | NULL
2 | Sample | Some | NULL
3 | Sample | Some | NULL
4 | Sample | Some | NULL
5 | Sample | Some | NULL
requests_votes structure is:
id | requestid | user_id
------------------------
10 | 2 | 2556
18 | 2 | 2522
33 | 3 | 120
44 | 2 | 1559
98 | 5 | 253
width the following query im able to calculate how votes have an item(request)
SELECT `requestid` , count( requests_votes.id ) AS totals
FROM `requests_votes`
INNER JOIN `requests` ON requests.id = requests_votes.requestid
GROUP BY `requestid`
output:
requestid | totals
------------------
2 | 3
3 | 1
5 | 1
i want to populate the hits column inside requests table with count( requests_votes.id ) result.
I've go trough the similar questions asked here on stackoverflow, but couldn't find the solution.
You can use JOIN in an UPDATE query:
UPDATE `requests` T1 JOIN
(SELECT `requestid` , count( requests_votes.id ) AS totals
FROM `requests_votes`
INNER JOIN `requests` ON requests.id = requests_votes.requestid
GROUP BY `requestid`) T2 ON T1.id=T2.requestid
SET T1.hits=T2.totals

Selecting first row that matches a criteria in Groupby

Given the following messages table where channel is a particular chat session, and user1 and user2 are the users in the chat:
+---------+-------+-------+------------+
| channel | user1 | user2 | message |
+---------+-------+-------+------------+
| 5 | 15 | 8 | Hello |
| 5 | 15 | 8 | I'm John |
| 5 | 8 | 15 | Hi John |
| 6 | 9 | 15 | yo |
| 6 | 15 | 9 | heyo |
| 6 | 9 | 15 | you here? |
| 8 | 15 | 10 | Hi |
| 8 | 15 | 10 | you there? |
+---------+-------+-------+------------+
I'd like to group by the channel and select the first response row (the first row where the second person said something). If the second person never responded as in channel 8, then they don't need to show up in the output.
So my expected output would be this:
+---------+-------+-------+---------+
| channel | user1 | user2 | message |
+---------+-------+-------+---------+
| 5 | 8 | 15 | Hi John |
| 6 | 15 | 9 | heyo |
+---------+-------+-------+---------+
Note that there is a timestamp column, just forgot to include it. Any help would be appreciated, been searching all over for a solution an have yet to come up with any. Thanks.
Assuming that you have a timestamp column, you can get the user2 for the first message as:
select m.*
from messages m
where not exists (select 1
from messages m2
where m2.channel = m.channel and
m2.timestamp < m.timestamp
);
So, if you want the first message from this, you can use the group_concat()/substring_index()` trick:
select m.channel, m.user1, m.user2,
substring_index(group_concat(m2.messages order by m2.timestemp separator '|'), '|', 1)
from messages m join
(select m.*
from messages m
where not exists (select 1
from messages m2
where m2.channel = m.channel and
m2.timestamp < m.timestamp
)
) mfirst
on m.channel = mfirst.channel and
m.user1 = mfirst.user2
group by m.channel, m.user1, m.user2;
Not entirely convinced myself. Feel free to improve.
The limit 1 relies on the table being read top down every time.
And I suspect all the different selects can be done more elegantly.
But at least it gives the required result for the sample data :)
SELECT channelchat.channel,
(SELECT user2
FROM chat firstline
WHERE firstline.channel = channelchat.channel
LIMIT 1) seconduser,
(SELECT user1
FROM chat firstline
WHERE firstline.channel = channelchat.channel
LIMIT 1) firstuser,
(SELECT message
FROM chat secondline
WHERE secondline.channel = channelchat.channel
AND secondline.user1 = seconduser
LIMIT 1) response
FROM chat channelchat
GROUP BY channelchat.channel
HAVING response IS NOT NULL
sqlfiddle

Store time between records

I have a table called asset_usages which records the viewing of an asset by a viewer. The relevant fields are
id (int)
asset_id (int)
viewer_type (string)
viewer_id (int)
viewed_at (datetime)
I have a new field i just added called time_between_viewings, which is an int field representing seconds. I want to set this to the time, in seconds, since that asset was last viewed. So, if i had these four records:
+-----+----------+-----------+-------------+---------------------+-----------------------+
| id | asset_id | viewer_id | viewer_type | viewed_at | time_between_viewings |
+-----+----------+-----------+-------------+---------------------+-----------------------+
| 506 | 7342 | 1182 | User | 2009-01-05 11:10:01 | NULL |
| 509 | 7342 | 1182 | User | 2009-01-05 11:12:47 | NULL |
| 514 | 6185 | 1182 | User | 2009-01-05 11:14:28 | NULL |
| 524 | 6185 | 1182 | User | 2009-01-05 11:28:18 | NULL |
| 618 | 1234 | 1182 | User | 2009-01-05 11:29:03 | NULL |
| 729 | 1234 | 1182 | User | 2009-01-05 11:29:01 | NULL |
+-----+----------+-----------+-------------+---------------------+-----------------------+
then time_between_viewings should be set as follows:
+-----+----------+-----------+-------------+---------------------+-----------------------+
| id | asset_id | viewer_id | viewer_type | viewed_at | time_between_viewings |
+-----+----------+-----------+-------------+---------------------+-----------------------+
| 506 | 7342 | 1182 | User | 2009-01-05 11:10:01 | NULL |
| 509 | 7342 | 1182 | User | 2009-01-05 11:12:47 | 166 |
| 514 | 6185 | 1182 | User | 2009-01-05 11:14:28 | NULL |
| 524 | 6185 | 1182 | User | 2009-01-05 11:28:18 | 830 |
| 618 | 1234 | 1182 | User | 2009-01-05 11:29:03 | 2 |
| 729 | 1234 | 1182 | User | 2009-01-05 11:29:01 | NULL |
+-----+----------+-----------+-------------+---------------------+-----------------------+
where 166 and 830 are the time difference between each pair, in seconds.
What would be the sql to populate this field? I can't quite figure it out.
IMPORTANT NOTE: the data is not always inserted into the db in chronological order. Ie, you could have two records A and B, where B has a higher id but A has a later value for viewed_at. So, looking for the first matching record with a lower id would not necesarily give you the previous viewing by the same person - you'll need to examine all the records in the database.
thanks! max
EDIT - stated that time_between_viewings is an int field representing seconds.
EDIT - added a couple of rows as an example of a row with a higher id but earlier timestamp
EDIT - i just realised that i didn't stipulate the question properly. The time_between_viewings should be equal to the time since the asset was last viewed by the same viewer, ie the time between the record and the previous (based on viewed_at) record that has the same asset_id, viewer_id and viewer_type. The example data i gave still holds, but i could have put in some different viewer_id and viewer_type values to flesh the example out a bit.
If would be helpful if you prepared sample table and data inserts.
Read this link to learn why it is so important if you want to get help : http://tkyte.blogspot.com/2005/06/how-to-ask-questions.html
This time I created it for you, click on this link: http://sqlfiddle.com/#!2/9719a/2
And try this query (you will find this query together with sample data under the above link) :
select alias1.*,
timestampdiff( second, previous_viewed_at, viewed_at )
as time_between_viewings
from (
select alias.*,
(
select viewed_at from (
select
( select count(*) from asset_usages y
where x.asset_id = y.asset_id
and y.viewed_at < x.viewed_at
) as rn,
x.*
from asset_usages x
) xyz
where xyz.asset_id = alias.asset_id
and xyz.rn = alias.rn - 1
) previous_viewed_at
from (
select
( select count(*) from asset_usages y
where x.asset_id = y.asset_id
and y.viewed_at < x.viewed_at
) as rn,
x.*
from asset_usages x
) alias
) alias1;
This SELECT statement will give you the right data. You might need to do the update in chunks, though.
You can drop the ORDER BY clause for the UPDATE statement. As written, the derived data doesn't depend on the order of rows in the outer SELECT statement.
select asset_id, viewer_id, viewer_type, viewed_at,
prev_viewed_at, timestampdiff(second, prev_viewed_at, viewed_at) elapsed_sec
from (select asset_id, viewer_id, viewer_type, viewed_at,
(select max(t2.viewed_at)
from Table1 t2
where t2.viewed_at < Table1.viewed_at
and t2.asset_id = Table1.asset_id
and t2.viewer_id = Table1.viewer_id
) prev_viewed_at
from Table1
)t3
order by asset_id, viewer_id, viewed_at;

mysql query for messaging system

I'm developing messaging system and have a trouble writing down the query(MYSQL).
In the nutshell query should return an array of one last messages for each conversation user to user.
CREATE TABLE 'tbl_message'('id' int(10) AUTO_INCREMENT,
'from_user' int(10),
'to_user' int(10),
'reply_to' int(10),
'subject',
'body' text,
'status' tinyint(3),
'deleted_from_sender' tinyint(3),
'deleted_from_recipient' tinyint(3)',
'created_on' datetime,
I wrote query:
SELECT * FROM tbl_message s1 WHERE s1.created_on IN (
SELECT MAX(s2.created_on)
FROM tbl_message s2 WHERE s1.from_user=".$userID." OR s2.to_user=".$userID."
GROUP BY s2.from_user, s2.to_user)
AND status = 0";
That works fine but give me 2 last messages from from_user and to_user, instead of 1 last message from both. That because of GROUP BY of course, now the question is how I could find the max created_on(actually mysql datestamp) in subquery? Or any other solution will be appreciated.
Appreciate any help or advice.
After 2 days digging stackoverflow and mysql manuals hope for help from DB prof:)
UPD:
some data for example
+----+-------+--------+--------+---------------------+--------+---------+
| id | from_user | to_user | subject | created_on | status |
+----+-------+--------+--------+---------------------+--------+---------+
| 1 | 68 | 128 | somesubject1 | 2013-07-01 21:31:29 | 0 |
+----+-------+--------+--------+---------------------+--------+---------+
| 2 | 128 | 68 | somesubject2 | 2013-07-01 21:41:29 | 0 |
+----+-------+--------+--------+---------------------+--------+---------+
| 3 | 128 | 68 | somesubject3 | 2013-07-01 21:51:29 | 0 |
+----+-------+--------+--------+---------------------+--------+---------+
| 4 | 68 | 226 | somesubject4 | 2013-07-01 22:01:29 | 0 |
+----+-------+--------+--------+---------------------+--------+---------+
output of query
| 3 | 128 | 68 | somesubject3 | 2013-07-01 21:51:29 | 0 |
+----+-------+--------+--------+---------------------+--------+---------+
| 4 | 68 | 226 | somesubject4 | 2013-07-01 22:01:29 | 0 |
You could use this query to get the maximum created_on date between two users:
SELECT
LEAST(from_user, to_user) user1,
GREATEST(from_user, to_user) user2,
MAX(created_on) max_created_on
FROM
tbl_message
GROUP BY
LEAST(from_user, to_user),
GREATEST(from_user, to_user)
and the query you are looking for is probably this:
SELECT t.*
FROM
tbl_message t INNER JOIN (
SELECT
LEAST(from_user, to_user) user1,
GREATEST(from_user, to_user) user2,
MAX(created_on) max_created_on
FROM
tbl_message
WHERE from_user=68 or to_user=68
GROUP BY
LEAST(from_user, to_user),
GREATEST(from_user, to_user)) M
on t.created_on = M.max_created_on
AND LEAST(t.from_user, t.to_user)=user1
AND GREATEST(t.from_user, t.to_user)=user2
See fiddle with both queries here.