GROUP BY messages MySQL - php

I started developing chat application for my website.
First I did some javascript part, before I got to backend.
And Now just created database structure:
CREATE TABLE IF NOT EXISTS `wp_bp_my_chat` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`from` varchar(255) NOT NULL DEFAULT '',
`to` varchar(255) NOT NULL DEFAULT '',
`message` text NOT NULL,
`sent` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`recd` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `to` (`to`),
KEY `from` (`from`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
Now, having this databse, I want to make a request to see all messages grouped by "from" OR "To"
Think of it as facebook messages, when you go to actual page, there is a left sidebar with messages grouped by conversation.
the output should be like:
conversation between "user_1" and "user_2" (unread) 2 hours ago
conversation between "user_1" and "user_3" (unread) 3 hours ago
converstation between "user_1" and "user_5" 5 hours ago
so my messages are grouped like conversations.
I might have 10 message from user_2 but it should be displayed as one (and info from last one)
Any Ideas how I go next?
As I have not done any php side yet You can even suggest changing database to adjast for your solution.
Thanks.

I assume you would run this for one person ('user_1') for their conversations, which means they can be either the from or the to. I also assume that it make no difference if they are the from or the to, but to group by the other person in the conversation. If so, try this. (You should put some sample data in SQLFiddle for testing)
SELECT MostRecent.MainPerson AS MainPerson
, MostRecent.OtherPerson AS OtherPerson
, MostRecent.Sent AS Sent
, IF(wp_bp_my_chat.recd = 0, 'Unread','Read') AS Status
FROM wp_bp_my_chat
JOIN (
SELECT 'user_1' AS MainPerson
, IF(msgs.`from` = 'user_1',msgs.to, msgs.`from`) AS OtherPerson
, MAX(msgs.sent) AS sent
FROM wp_bp_my_chat AS msgs
WHERE msgs.`from` = 'user_1' OR msgs.`to` = 'users_1'
GROUP BY MainPerson, OtherPerson) AS MostRecent
ON (wp_bp_my_chat.`from` = MostRecent.MainPerson OR wp_bp_my_chat.`to` = MostRecent.MainPerson)
AND (wp_bp_my_chat.`from` = MostRecent.OtherPerson OR wp_bp_my_chat.`to` = MostRecent.OtherPerson)
AND MostRecent.sent = wp_bp_my_chat.sent
ORDER BY sent DESC

To get the results you described in your update, use:
SELECT count(*), max(sent)
FROM wp_bp_my_chat
WHERE to = 'name of recipient'
GROUP BY from
count(*) gives you the number of messages and max(sent) gives you the time of the latest message, which you can use to calculate the "hours ago" part of the output.
I don't see a flag in your table for whether the message has been read. You'd need to add that in order to add the "(unread)" text.

This can be done with a simple group by query:
SELECT * FROM `wp_bp_my_chat` WHERE `to` = {my_id} GROUP BY `from`
This will give you all the chats that I as a user have had.

Related

How to make and display notifications in PHP MYSQL

Before I dive in, This example below is what I hope to achieve.
Meanwhile I Have a database table that is structured like so.
CREATE TABLE `notifications` (
`id` int(11) NOT NULL,
`recipient_id` int(11) NOT NULL,
`sender_id` int(11) NOT NULL,
`unread` tinyint(4) NOT NULL DEFAULT '1',
`type` varchar(255) NOT NULL,
`reference_id` int(11) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
`recipient_id` - Notification Reciever
`sender_id` - Notification Sender
`unread` - Mark if notificaton has been read or not
`type` - Holds types of notification, Comment, Likes etc
`reference_id` - the reference like a post id
`created_at` - Time noti was created.
and a PHP to fetch data from the database table.
$query = $this->database->query("SELECT recipient_id, unread, type, reference_id, post_title, post_name, COUNT(reference_id)
FROM notifications n INNER JOIN posts p ON p.id = n.reference_id WHERE n.recipient_id = $user AND n.type = 'post_comment' AND n.unread = 1
GROUP BY n.reference_id HAVING COUNT(n.reference_id) >= 1 ORDER BY unread DESC LIMIT 8")->fetchAll();
return $query;
I'm grouping the results I get by the reference_id if its >= 1
this reason is so I don't get duplicate notification that has the same reference_id.
with this query so far I am able to get the data from the database table and display like so.
Someone commented on your post "I love to code"
but I want to display to the user like the example above or like this below.
James, John and others commented on your post "I Love to Code"
thats if there is more than 1 or 2 sender_id with the same reference_id
this is where I am stuck and don't know which other step to take, please any help I can get is appreciated.
thanks

Select last distinct pair

Ok we have inbox table where we keep messages that users send to each other. Here is the table:
CREATE TABLE IF NOT EXISTS `inbox` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fromid` int(10) unsigned NOT NULL DEFAULT '0',
`toid` int(10) DEFAULT NULL,
`message` text CHARACTER SET utf8 NOT NULL,
`time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
KEY `toid` (`toid`),
KEY `fromid` (`fromid`),
KEY `fromid_2` (`fromid`,`toid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;
fromid and toid are id's of the users. We have their id's, times when the message is sent. What we need is a query that would return all messages that are not replied by 'our users' (admins).
Table accounts keeps track of users. To simplify:
CREATE TABLE IF NOT EXISTS `accounts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`our` int(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
So basically, we need a query that gives us the users WHOSE messages WERE NOT ANSWERED by admins (our users), their count and the date of the last message they sent to ADMIN, ordered from last to oldest.
So far we only have some basic queries, we didn't come up with anything reasonable that I could post.
Thanks in advance.
EDIT: From what I see we first need to find last interaction from two DISTINCT users in inbox table... then check & filter only those that were sent TO our users
How about this?
SELECT i.* FROM inbox as i
WHERE (i.toid, i.fromid) NOT IN
(SELECT i2.fromid, i2.toid FROM inbox as i2 WHERE i2.`time` >= i1.`time` AND i2.id = 1);
Another way using join:
SELECT DISTINCT i1.*
FROM inbox as i1 LEFT JOIN inbox as i2
ON i1.toid = 1 AND
i1.fromid = i2.toid AND
i1.toid = i2.fromid AND
i1.`time` <= i2.`time`
WHERE i2.id IS NULL;
Two possible solutions presented below: LEFT JOIN solution should perform better.
LEFT JOIN solution
SELECT
i.fromid, COUNT(*) AS unread, MAX(i.time) AS lastmsg
FROM inbox AS i
INNER JOIN accounts AS a
ON i.toid = a.id
LEFT JOIN inbox AS i2
ON i.fromid = i2.toid AND i.toid = i2.fromid AND i.time <= i2.time
WHERE a.our = 1 AND i2.id IS NULL
GROUP BY i.fromid
ORDER BY lastmsg DESC;
NOT IN solution
SELECT
i.fromid, COUNT(*) AS unread, MAX(i.time) AS lastmsg
FROM inbox AS i
INNER JOIN accounts AS a ON i.toid = a.id
WHERE a.our = 1 AND
(i.toid, i.fromid)
NOT IN (SELECT i2.fromid, i2.toid FROM inbox AS i2 WHERE i2.time >= i.time)
GROUP BY i.fromid
ORDER BY lastmsg DESC;

MySQL "NOT IN" Query Optimization

Optimizng MySQL queries isn't my expertise, so I was wondering if someone could help me formulate the most optimal query here (and indices).
As background, I'm trying to find a distinct visitor id within a table of transactions with certain where criteria (date range, not a certain product, etc. as you see in the query below). Transactions and visitors have a one to many relationship, so there can be many transactions to a single visitor.
Another requirement for the results is that if a visitor_id is found in the result, it must be the first instance of a visitor_id (by date_time) in the entire table. In other words, the visitor_id should only exist in the date range set in the primary query and at no time beforehand.
Here's what I've put together so far. It uses NOT IN and a subquery, but this doesn't seem ideal because the query takes between 2-3 seconds being that the table has over 500k records. I've tried a few variations of indices, but nothing seems to really work.
Here's the query.
SELECT DISTINCT visitor_id, date_time
FROM pt_transactions
WHERE visitor_id NOT IN (SELECT visitor_id FROM pt_transactions WHERE date_time < '$this->_date_time_start')
AND campaign_id = $this->_campaign_id
AND a_aid = '$a_aid'
AND date_time >= '$this->_date_time_start'
AND date_time <= '$this->_date_time_end'
AND product_id != 65
And here's the complete table structure.
CREATE TABLE IF NOT EXISTS `pt_transactions` (
`id` int(32) NOT NULL AUTO_INCREMENT,
`type` varchar(2) NOT NULL COMMENT 'New Lead (NL), Raw Optin (RO), Base Sale (BS), Upsell Sale (US), Recurring Sale (RS), Base Refund (BR), Upsell Refund (UR), Recurring Refund (RR), Unknown Refund (XR), or Chargeback (C)',
`date_time` datetime NOT NULL,
`amount` varchar(255) NOT NULL,
`a_aid` varchar(255) NOT NULL,
`subid1` varchar(255) NOT NULL,
`subid2` varchar(255) NOT NULL,
`subid3` varchar(255) NOT NULL,
`product_id` int(16) NOT NULL,
`visitor_id` int(32) NOT NULL,
`campaign_id` int(16) NOT NULL,
`last_click_id` int(16) NOT NULL,
`trackback_type` varchar(255) NOT NULL COMMENT 'Shows if the transaction is tracked back to the original visitor via cookie or via IP. Usually only applies to sales via pixel.',
`original_transaction_id` int(32) NOT NULL COMMENT 'Reference to original transaction id, in this table, if type is RS, R, or C',
`recurring_transaction_id` varchar(32) NOT NULL COMMENT 'Reference to existing RecurringTransaction if type is RS',
PRIMARY KEY (`id`),
KEY `visitor_id` (`visitor_id`),
KEY `campaign_id` (`visitor_id`,`campaign_id`,`amount`,`product_id`),
KEY `transaction_retrieval_group` (`campaign_id`,`date_time`,`a_aid`),
KEY `type` (`type`),
KEY `date_time` (`date_time`),
KEY `original_source` (`campaign_id`,`a_aid`,`date_time`,`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=574636
You can try NOT EXISTS
SELECT DISTINCT visitor_id, date_time
FROM pt_transactions t
WHERE campaign_id = $this->_campaign_id
AND a_aid = '$a_aid'
AND date_time >= '$this->_date_time_start'
AND date_time <= '$this->_date_time_end'
AND product_id != 65
AND NOT EXISTS
(
SELECT *
FROM pt_transactions
WHERE visitor_id = t.visitor_id
AND date_time < '$this->_date_time_start'
)
Do EXPLAIN <query> and see how your indices are used. If you want you can post results in your question in a textual form.
From your query what i can understand is that...
Their is no need to write NOT IN Statement...
Because, you are already keeping a check for
date_time >= '$this->_date_time_start'
so thier is no need to check date_time < '$this->_date_time_start' in not NOT IN statement.
Only below should work fine :)
SELECT DISTINCT visitor_id, date_time
FROM pt_transactions
WHERE
AND campaign_id = $this->_campaign_id
AND a_aid = '$a_aid'
AND date_time >= '$this->_date_time_start'
AND date_time <= '$this->_date_time_end'
AND product_id != 65

index uniq key does not seems to work

I'm trying to do a kind of friend request for my chat,
so I set a table called cyb_user_friendlist
then I've put some tables like that :
1 id_friendlist int(11) AUTO_INCREMENT
2 from int(11)
3 to int(11)
4 couple varchar(11)
5 accept int(11)
6 block int(11)
so for each friend request an insert is done to this table with id of sender into from and id of receiver into to, but to make sure that there is only one request per couple I added a field called couple in which there is the concatenation of from and to with a vertical separator |. this field has a uniq key because I want to prevent from multiple records.
the only thing is that it does not seems to work, actualy I added my uniq key to this fields and a primary key to the id_friendlist but it does not work, I can send many request as wanted...
my request $sql to do that is the one below :
$query = "INSERT INTO `cyb_users_friendlist` SET
`from` = {$from},
`to` = {$to},
`couple` = '{$from}|{$to}'";
I really do not know where I'm wrong...
anykind of help will be much appreciated.
$query = "INSERT INTO `cyb_users_friendlist` SET
`from` = $from,
`to` = $to,
`couple` = concat('$from','|','$to')'";
Why are you adding another field which is concatenated of two another when you can just add unique index?
mysql combined unique keys
ALTER TABLE `YOUR TABLE` ADD UNIQUE `unique` ( `from` , `to` )

Facebook like messaging system - Sort by last reply

I am building a messaging system similar to facebook's (where it displays messages as threads).
My current table design is:
CREATE TABLE IF NOT EXISTS messages (
mid int(11) NOT NULL auto_increment,
subject text NOT NULL,
message text NOT NULL,
fromid varchar(255) NOT NULL default '',
toid varchar(255) NOT NULL default '',
status varchar(255) NOT NULL default '',
date varchar(255) NOT NULL default '',
time varchar(255) NOT NULL,
PRIMARY KEY (mid)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=2825 ;`
I am retrieving results with this select statement:
SELECT
IF(messages.toid = '$uid' OR messages.toid = '$uid', messages.fromid, messages.toid) friend1,
messages.message, messages.fromid, messages.toid, messages.date, messages.status, messages.time
FROM messages
WHERE (messages.toid='$uid' OR messages.fromid='$uid')
AND messages.status!='2'
GROUP BY friend1 ASC
ORDER BY messages.time DESC, messages.mid DESC
This gives me the right results except for it displays the first post from a thread, I would like for it to display the most recent post in a thread.
What am I doing wrong?
This is a nice article directly related to your question: http://kristiannielsen.livejournal.com/6745.html
Taking a wild stab at your specific problem, your query would probably need to look something like below (untested!):
SELECT
IF(derived_messages.toid = '$uid', derived_messages.fromid,
derived_messages.toid) friend1,
derived_messages.message, derived_messages.fromid, derived_messages.toid,
derived_messages.date, derived_messages.status, derived_messages.time
FROM
(SELECT *
FROM messages
ORDER BY time desc) derived_messages
WHERE (derived_messages.toid='$uid' OR derived_messages.fromid='$uid')
AND derived_messages.status!='2'
GROUP BY friend1 ASC
ORDER BY derived_messages.time DESC, derived_messages.mid DESC
Btw this clause looked fishy to me (in your original post):
IF(messages.toid = '$uid' OR messages.toid = '$uid',
messages.fromid, messages.toid) friend1
What's the "OR" doing between identical conditions? You can probably skip the second condition, I think.

Categories