I have created a messaging system for users, it allows them to send a message to another user. If it is the first time they have spoken then a new conversation is initiated,
If you're asking for a way to be able to keep all of your current functionality and work flows, yet keep the data in a single table I think you're pretty close.
Instead of having the conversationId
be a key to a different table, I would instead have it point to the ID of the message that began the conversation. This would create a parent-child relationship between messages that began a conversation and all those that followed after it. To be able to see all conversations, you would just select all messages where the conversationId
is null. Below is a representation of a 2 message conversation:
+----+---------+------+------------------+----------------+--------+----------+
| id | message | read | time | conversationId | toUser | fromUser |
+----+---------+------+------------------+----------------+--------+----------+
| 1 | test 1 | 0 | (some timestamp) | null | 3 | 4 |
| 2 | test 2 | 0 | (some timestamp) | 1 | 4 | 3 |
+----+---------+------+------------------+----------------+--------+----------+
The conversation was initiated by user 3. All messages in the conversation can be filter by conversationId
. One limitation of this design is that only 2 users can be apart of the conversation.
Update
You could get the last message given a conversation id this way:
SELECT id, message
FROM userMessages
WHERE conversationId = {conversationId}
ORDER BY time DESC
LIMIT 1
it is being used on fiverr.com and www.infinitbin.com. I developed the infinitbin own. It has two databases like yours too. the inbox table:-
+----+--------+----------+-------------+------------+--------------------------------+
| id | useridto | useridfrom | conversation | last_content | lastviewed | datecreated|
+----+--------+----------+-------------+------------+--------------------------------+
This table is very important, used to list the conversations/inbox. The last_content field is 140 charaters from the last message between the conversations. lastviewed is an integer field, the user who lasts sends a message is the last viewed, if the other user in the conversation reads the message. it gets updated to NULL. Therefore to get notifications, you for lastviewed that is not null and not the logged in user's id.
The conversation field is 'userid-userid', therefor strings. To check if users have started a conversation, you concatenate there user_ids with a hyphen and check it.
This kind of messaging system is a very complicating one.
The second table is quite simple.
+----+--------+----------+-------------+-------+
| id | inboxid | userid | content | datecreated|
+----+--------+----------+-------------+-------+
If you want to simplify your query you should add the last message id in your table userconversation
:
ALTER TABLE userconversation ADD lastusermessageid
then each time you add a new message you should update your table userconversation :
INSERT INTO userconversation(userId, friendId, lastusermessageid)
VALUES (:userId, :friendId, :lastusermessageid)
ON DUPLICATE KEY UPDATE lastusermessageid = VALUES(lastusermessageid)
and finally add indexes on all foreign keys :
SELECT
c.id,
c.userId,
c.friendId,
m.message,
m.read,
UNIX_TIMESTAMP(m.time),
user1.username,
user2.username
FROM
userconversation c
INNER JOIN usermessages m ON c.lastusermessageid = m.id
INNER JOIN user user1 ON c.userId = user.id
INNER JOIN user user2 ON c.friendId = user.id
WHERE
c.userId = :userId OR c.friendId = :userId
ORDER BY
m.id DESC
LIMIT 10
How to create a fast Facebook-like messages system. tested and widely used by Arutz Sheva users - http://www.inn.co.il (Hebrew).
create a "topic" (conversation) table:
CREATE TABLEpb_topics
(t_id
int(11) NOT NULL AUTO_INCREMENT,t_last
int(11) NOT NULL DEFAULT '0',t_user
int(11) NOT NULL DEFAULT '0', PRIMARY KEY (t_id
), KEYlast
(t_last
) ) ENGINE=InnoDB AUTO_INCREMENT=137106342 DEFAULT CHARSET=utf8
create link between user and conversation:
CREATE TABLEpb_links
(l_id
int(11) NOT NULL AUTO_INCREMENT,l_user
int(11) NOT NULL DEFAULT '0',l_new
int(11) NOT NULL DEFAULT '0',l_topic
int(11) NOT NULL DEFAULT '0',l_visible
int(11) NOT NULL DEFAULT '1',l_bcc
int(11) NOT NULL DEFAULT '0', PRIMARY KEY (l_id
) USING BTREE, UNIQUE KEYtopic-user
(l_topic
,l_user
), KEYuser-topicnew
(l_user
,l_new
,l_topic
) USING BTREE, KEYuser-topic
(l_user
,l_visible
,l_topic
) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=64750078 DEFAULT CHARSET=utf8
create a message
CREATE TABLEpb_messages
(m_id
int(11) NOT NULL AUTO_INCREMENT,m_from
int(11) NOT NULL,m_date
datetime NOT NULL DEFAULT '1987-11-13 00:00:00',m_title
varchar(75) NOT NULL,m_content
mediumtext NOT NULL,m_topic
int(11) NOT NULL, PRIMARY KEY (m_id
), KEYdate_topic
(m_date
,m_topic
), KEYtopic_date_from
(m_topic
,m_date
,m_from
) ) ENGINE=InnoDB
A conversation can be with 2 or more friends (BCC was added like email, but you can skip it).
Insert a new message: 1. Create new topic 2. Create Links for users (from/to) 3. Add Message (4. Update users cache table - user have messages)
Add Message to topic: Add Message
Select folder:
select
z.*, group_concat(u_name) as users_name from
(select max(m_id) as m_id, m_topic as t_id, m_From, m_title,m_date, l_new
from pb_links as l1, pb_messages
where l1.l_user=<user> and m_from < If(inbox, "<>", "=") > and m_topic=l_topic and l1.l_visible=1
group by m_topic order by m_id desc limit " & iPage * 35 & ",35) z
left join pb_links l2 on (l2.l_topic=t_id)
left join users on (l_user=u_id and l_bcc=0 and l_user<user>)
group by l_topic order by m_date desc;
In details:
The first is the inner select - this is the fastest way (I was check about 7 other options, checked also in Percona/MariaDB versions) to get all messages, and get also the last message to display in the list. Also look in the inner IF - in inbox, the last message is anyone but not me, and In outbox - the opposite. LIMIT used for paging.
The outer one used to add user list (just name comma name string) and more information for only one message per topic, and after paging (I need to add user list just to the 35-per-page messages, and not for all my large history).
Also, I wrote in hebrew here: http://blogs.microsoft.co.il/blogs/moshel/archive/2010/08/12/quot-x-quot.aspx to create a simple cache table, and forbid the workload of select count from busy message table.
If you can only have one conversation between users, I don't see a reason for a dedicated conversations table. For this query to work fast, you would need a composite index on (user, message_id)
which is impossible if these fields are in different tables. Move user_id
and friend_id
to the userconversations
. This will make your table 8
bytes per record heavier (even assuming 8
-byte identifiers) which is hardly a problem for a table containing text messages.
If you have few conversations per user with many messages in each, use this:
SELECT um.*
FROM (
(
SELECT MAX(id) AS messageId
FROM usermessages m1
WHERE user_id = :me
GROUP BY
friend_id
ORDER BY
messageId DESC
LIMIT 10
)
UNION ALL
(
SELECT MAX(id) AS messageId
FROM usermessages m1
WHERE frient_id = :me
GROUP BY
user_id
ORDER BY
messageId DESC
LIMIT 10
)
) q
JOIN usermessages um
ON um.id = q.messageId
ORDER BY
id DESC
LIMIT 10
Create separate indexes on user_id
and friend_id
If you have many conversations with few messages in each, use this query:
(
SELECT *
FROM usermessages um
WHERE user_id = :me
AND id =
(
SELECT MAX(id)
FROM usermessages umi
WHERE umi.user_id = um.user_id
AND umi.friend_id = um.friend_id
)
ORDER BY
id DESC
LIMIT 10
)
UNION ALL
(
SELECT *
FROM usermessages um
WHERE frient_id = :me
AND id =
(
SELECT MAX(id)
FROM usermessages umi
WHERE umi.user_id = um.user_id
AND umi.friend_id = um.friend_id
)
ORDER BY
id DESC
LIMIT 10
)
ORDER BY
id DESC
LIMIT 10
The idea behind this query is that it just descends all messages for the given user, checking that each message is the last in its conversation. This may be much faster than sorting all last messages for all conversations (if you have many of them).
For this to work fast, create indexes on
friend_id
user_id, friend_id
I haven't tested this approach as I don't have access to mysqldb right now. But, I think you should be able to get this done by using a ranking function. Since mysql doesn't have an equivalent of row_number function of Oracle I think you can do it like this:
Select * from (
Select
uc.id,
uc.user_id,
uc.friend_id
um.message
um.read,
um.time,
@rownum := IF(@prev_val = um.conversation_id, @rownum + 1, 1) AS rank,
@prev_val := um.conversation_id
From
userconversation uc,
usermessages um,
(select @row_num:=1) rows,
(select @prev_val:='') partitions
Where
uc.id=um.conversation_id
and c.userId = 222 OR c.friendId = 222
Order By
um.conversation_id,um.id desc
)t where t.rank=1