Query for private messaging system - php

I'm making private messaging system using mysql. Created this tables:
1) users (id, name)
2) messages(id, text, created)
3) user_has_messages(id, user_id, message_id, is_sender)
Table user_has_messages stores messaging history, so there are 2 rows(for "sender" user and for "receiver" user.) per 1 message. 2 rows per message because sender should see his message even if receiver deleted it.
So i need to fetch list of all dialogs for concrete user with last message in it. It should be easier to understend if you take a look a this pic: Explanation
The problem is that i cannot construct a proper query for this task. Maybe bad db design?

Looks like this query is what i need:
SELECT * FROM users_has_messages uhm1
WHERE uhm1.message_id=(
SELECT message_id FROM users_has_messages uhm2
WHERE (uhm1.receiver_id=uhm2.receiver_id AND uhm1.sender_id=uhm2.sender_id)
OR uhm1.receiver_id=uhm2.sender_id ORDER BY message_id DESC limit 1)
AND user_id=1

I believe the database design may be wrong, because if the recipient deletes his message (by deleting the user_has_messages row) then the sender can no longer see who they sent it to - information is lost.
If a message always has one sender and one recipient, then I would have the tables like:
1) users (id, name)
2) messages(id, text, created, sender_id, recipient_id,
deleted_by_sender, deleted_by_recipient)
Even with this simplified design the SQL for your requirement is a bit complicated:
select m.recipient_id, m.text
from messages m
where m.sender_id = ?
and m.created = (select max(created)
from messages m2
where m2.sender_id = m.sender_id
and m2.recipient_id = m.recipient_id
and m2.deleted_by_sender = 0
and m2.deleted_by_recipient = 0);
(and that assumes that (sender_id, recipient_id, created) is a unique key).

Related

Displaying message status: read and unread (differs from other users) in PHP/MySQL messaging system

I am displaying the message status that differs from each user. Let's say user1 sends a message to user2, user1's message status then sets to read, while user2's message is set to unread by default. It will be updated after user2 clicks the message.
So in these scenario, the message of user1 (from the inbox) will have a gray-colored font which indicates that the message is set to read (since user1 is the one who is sending). On the other side, user2 have a bold font that indicates that the message is unread.
Here is the first structure of the table:
message(messageid, fromid, toid, message, timestamp, status)
The problem here is that if I update the message status to read, it affects the other side (user2). So I add another column that will set the status differently from user1 and user2:
message(messageid, fromid, toid, message, timestamp, from_status, to_status)
Here, from_status is for the fromid and to_status is for toid. But I'm having a problem on how to use these values to display the status.
The PHP code of that I use during my first attempt is these:
<?php
$id = $_SESSION['id'];
$query = mysql_query("SELECT m.* FROM message m
LEFT JOIN message m2 ON (
(m.fromid=m2.fromid AND m.toid=m2.toid) OR
(m.fromid=m2.toid AND m.toid=m2.fromid)
) AND m.timestamp<m2.timestamp
WHERE (m.fromid='$id' OR m.toid='$id') AND m2.toid IS NULL ORDER BY timestamp DESC");
while ($message = mysql_fetch_array($query)) {
if ($message['status'] === 'unread') {
// bold font style will be applied
}
else {
// gray-colored font will be applied
}
}
?>
(The query fetches each conversation from every user with the latest conversation.)
These code works fine for the main user, which is user1, but affects the other side, which views that the message received from user2 is set to read instead or unread.
So, I'm having some trouble on what to do on the modified table, having 2 separate status each for each user. How can I get these done?
#andrewsi comment is quite nice, when you'll have for example many receivers. In your case it's only one additional field, so in my opinion it's not an overflow to use just one table.
Regarding your case you can do this in one simple sql:
SELECT m.*,
CASE
WHEN m.fromid = $id THEN m.from_status
WHEN m.toid = $id THEN m.to_status
END as read_status
FROM message m
WHERE
m.fromid = $id OR m.toid = $id
ORDER BY timestamp DESC;
And in your view you are only checking the read_status field
(Posted on behalf of the OP.)
I've managed to solve the problem, thanks to #Rafal Mnich. So I grab a part of his query and do a bit of modification, and it works.
Here's the query:
SELECT m.msgid, m.fromid, m.toid, m.content,
CASE
WHEN m.fromid = '$id' THEN m.frommsgstatus
WHEN m.toid = '$id' THEN m.tomsgstatus
END AS msgstatus
FROM msg m
LEFT JOIN msg m2
ON ((m.fromid=m2.fromid AND m.toid=m2.toid) OR (m.fromid=m2.toid AND m.toid=m2.fromid)) AND m.timestamp<m2.timestamp
WHERE (m.fromid='$id' OR m.toid='$id') AND m2.toid IS NULL
ORDER BY m.timestamp DESC
These displays the messages grouped by the sender fromid, which also displays the latest conversation you have from each of the senders. And it displays the correct message status in both sides of the users.

Writing php conversation messaging MySQL query

I am creating php messaging system based with conversations. And I got a problem.
I have a table with columns: id, to, from, msgtext, timesent, viewed, deleted.
I want to select just one conversation between to and from. Take a notice that if user to can write a message to user from.
How to select seperate conversations?
I am trying with this SQL:
SELECT to, from FROM pms WHERE to='$userid' OR from = $userid group by to ORDER by id desc LIMIT 50
But this does not work another way. Because if user1 wrote message to user2, and user2 replied to user1, and again if user1 replies and user2 replies, i see as 2 conversations, but it should be as one conversation.
It should be like this:
User sends a message to some other user. It should show in conversations list That other user's name.
If user gets a message from another user, he should see his name in the conversation list.
Try this code
SELECT `id`, `to`, `from`, `msgtext`, `timesent`, `viewed`, `deleted` FROM table_name
WHERE `to` = 'username_to' AND `from` = 'username_from'
LIMIT 1
OFFSET 0
If you understand the concept behind you will realize that you need to play only with OFFSET number to get the next record.
P.S. A shortcut to the LIMIT & OFFSET lines above is:
LIMIT 0, 1
Also, when you ask something on Stack Overflow you are supposed to show what you have tried, and ask why is not working, instead of simply asking for code.

How to set read unread flag in php message system

I'm developing a Facebook-type messaging system in PHP. I have a user_message table which contains sender_id, receiver_id, message, and flag as read/unread. After inserting a value in this table, I make it as unread and when the user clicks on the message notification, then the status will be updated as read.
It is working fine for single user. While sending message to multiple users though, I am storing receiver_id as comma separated values in the table. My problem is that how to set flag for multiple receivers? if I'm sending one message to 3 users, for example, then how can I set the read flag? Any help will be Appreciated.
While sending message to multiple users though, I am storing receiver_id as comma separated values in the table.
Don't misuse a string column to represent multiple values. It's impossible to index a column structured that way in most DBMSes, so you won't be able to search it efficiently. This is particularly significant for a "message recipient" structure, as it means that it's impossible to efficiently search for all messages received by a specific user. This would make many common operations like checking for new messages sent to a user, or viewing a user's mailbox, extremely slow on a large site.
Instead, if you want to have a single representation of a message sent to many people, rather than a separate message for each one, model this using two tables, e.g.
CREATE TABLE message (
id INTEGER PRIMARY KEY NOT NULL,
sender_id INTEGER NOT NULL,
message TEXT NOT NULL
);
CREATE TABLE message_recipient (
message_id INTEGER NOT NULL,
recipient_id INTEGER NOT NULL,
PRIMARY KEY (message_id, recipient_id)
);
With such a table structure, you can place a "read" flag on the message_recipient object, so that each recipient of a given message has a separate read status.
1) You can add field with name "users_reads" and put in it IDs of users, who read message, separating by comma. How to check if user reads message:
if (in_array($userId, explode(',', $row['users_reads']))) { ... }
2) You can add new table 'users_reads_messages' in your database with fields:
- message_id
- reader_id
3) you can make a copy of message with another reciever_id, I mean you put single user id in this field, for other users you make a copy of this message in your table.
p.s. sorry for my bad english
You can have a juntion table between message table and receiver table. For example MessageRead table. it has id(auto increment) message_id and receiver_id. If user with id=10 read the message with id=5, you need to execute this query:
INSERT INTO MessageRead(message_id, receiver_id) VALUES(5, 10)
For getting number of users that read the message with id=5, you can execute this query:
SELECT COUNT(*) FROM MessageRead WHERE message_id = 5
And, for checking if user with id = 10 read the message with id = 5, execute this query:
SELECT COUNT(*) FROM MessageRead WHERE message_id = 5 AND receiver_id = 10
If the result of this query is 1, it means the user has read the message, otherwise he has not read the message.

PHP - Messaging inbox system

I am developing a messaging inbox system. Users create message threads directed at another user. It will work as follows:
Message threads you receive appear in your inbox.
Message threads you create also appear in your inbox.
Message threads you delete are only deleted from your own inbox.
I have the following fields in my message_thread table:
id
from_user_id
to_user_id
subject
deleted
updated_at
I have managed to implement 1 and 2 using the following query:
SELECT * FROM message_thread mt
WHERE ((mt.from_user_id = 1 OR mt.to_user_id = 1) AND mt.deleted = 0)
ORDER BY mt.updated_at DESC
I am trying to figure out a way of implementing 3.
I assume new columns will need to be introduced (from_user_deleted and to_user_deleted). Is it possible to retrieve the correct resultset by extending the query or will this need to be done in the server side script (currently using PHP)?
EDIT: If user A deletes a thread and then user B sends a reply to that thread, it should re-appear in user A's inbox.
Try below code:
1.You can add new field as status
2.Status
1 = from_user_deleted
2 = to_user_deleted
Then Query
SELECT * FROM message_thread mt
WHERE ((mt.from_user_id = 1 OR mt.to_user_id = 1) AND mt.deleted = 0 AND mt.status<>2)
ORDER BY mt.updated_at DESC
In plain english:
Where
( The message is to you
OR
( the message is from you
AND
the message is not deleted )
)
I have managed to solve this by adding a new table:
message_thread_status
message_thread_id (FK)
user_id (FK)
deleted (default 0)
When a message_thread is created, it creates two entries in the message_thread_status table (one for each user in the message thread).
I then just need to find the record for the current user in the message_thread_status table and add in a condition to check for the value of deleted.
New query:
SELECT
* FROM message_thread mt
LEFT OUTER JOIN
message_thread_status mts ON mts.message_thread_id = mt.id
WHERE
((mt.from_user_id = 1 OR mt.to_user_id = 1)
AND (mts.user_id = 1 AND mts.deleted = 0))
ORDER BY
mt.updated_at DESC

PM system - previewing previous PMs

I am working on a PM system where I'd like to have the previous sent PMs for one conversation, listed above the last received PM. But my question is: how do I go about setting up such a table in a database? I toyed for a while about using an id for each specific conversation, but what would the source for that id be? I can't use auto increment (it seems), because I'm using it for the primary "id" column.
Or maybe there's a completely different way I can experiment with the already available columns (id, from, to, subject, message, sent, read, deleted); but how? Please help a lost man out.
You could add a origin_id column to your table that contains the id of the root/original message, or NULL if it's a new discussion (root).
Then you can get the root messages by filtering those than have origin_id = NULL and then group by origin_id to get the message thread.
Okay, so I have got it partly solved...
I used another table containing the one column which holds the subject of the PM. I also have a new column in the regular "pms" table that holds the same ID to be able to join the tables together.
However, when I select all the PMs to show them in the inbox, I have not found a way to group the conversations in order by if they're read or not. I'm currently using this SQL query:
SELECT *
FROM `pms`
JOIN `pm_conversations` ON (pms.ConvID = pm_conversations.ID)
WHERE pms.To='username'
GROUP BY pm_conversations.ID
ORDER BY pms.ID
I came up with this:
SELECT MAX(pms.ID) as pmIDS,
pms.*,
pm_conversations.*
FROM `pms`
JOIN `pm_conversations` ON (pms.ConvID = pm_conversations.ID)
WHERE `To`='".$UserActive."'
GROUP BY pm_conversations.ID
ORDER BY pmIDS DESC

Categories