How to set read unread flag in php message system - php

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.

Related

Adding Unique constraints for two fields and don't allow duplicate entries if both are repeating

Is there any way to stop duplicate entries only if two columns are repeating the same value.
I am using MySQL.
for example
I have one table "voting" and fields are
id
vote
user_id
message_id
In this user can enter up or down vote for a message.
I don't want the user to add multiple vote for the same message
i.e if user_id 1 votes up to message_id 1
then if the same user votes up again for same message , i don't want to allow this repeating process .
I mean is there any way to set unique constraints for fields user_id and message_id and don't allow to insert a row if user_id and message_id is repeating.
I know we can stop it using logical code using php.
I am expecting answers is there any way to do this using mysql only.?
This should work:
ALTER TABLE `table`
ADD CONSTRAINT uc_user_message UNIQUE (user_id ,message_id )

MySql query to pull the original message but not the linked messages

I am using PHP and MySQL to create messaging script, what i need to do is display the original messages in inbox and when the specific message is clicked only then show the chain of messages linked with the original message. The original message should be displayed for the person who sent it and the person who received it.
This is what i am inserting in my table
message_id
to_user_id
from_user_id
subject
message
original_message_id
when a new message is initiated the original_message_id is blank but whoever replies to this message, the column original_message_id then stores the same id as message_id to keep track of conversation.
Now to test this lets assume there are two user_ids in system 1 and 2. I sent the new message to to_user_id 2 from from_user_id 1 and the reply was sent as well from from_user_id 1 to to_user_id 2.
I have entries in my table without any problem. So far this is working correct. What i am stuck at is when i display the new message in inbox it displays two records the original message the the reply as well and this is the query i am using, please let me know what is wrong with this query
`SELECT * FROM tbl_messages WHERE `to_user_id` = '1' OR `from_user_id` = '1' AND original_id IS NULL`
Reason i am using both to_user_id and from_user_id so the system displays the original received message for the recipient and sent message for the sender
Any help will be greatly appreciated.
The AND operator has higher precedence than OR, so you need to add parentheses to get the intended parse.
SELECT * FROM tbl_messages WHERE (`to_user_id` = '1' OR `from_user_id` = '1') AND original_id IS NULL

Query for private messaging system

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).

How do I select rows in a MySQL table by column/field value?

i have table called messages which has field called (emails) with type text .
for example this field value is (email#domain.com , example#any.net . notme#yahoo.com , me#yahoo.com).
and what i want when i am logged in with (me#yahoo.com) to display only messages that has value of (me#yahoo.com).
how can it be done with mysql & php ?
SELECT * FROM MESSAGES WHERE EMAIL = 'me#yahoo.com';
but EMAIL should probably be indexed to avoid haveing to scan the whole table to find matching rows. And type TEXT is probably unnecessary. Emails are not likely to be longer than 100 characters, so maybe set the type to VARCHAR(100) or similar.
Or given that each user will have an id, it's perhaps better get them out by USER_ID (which should also be indexed):
SELECT * FROM MESSAGES WHERE USER_ID = ?;
where USER_ID is the ID in your USER table or equivalent.

Private Messaging System With Threading/Replies

I'm currently working on creating a private messaging system, (PHP/MySQL) in which users can send message to multiple recipients at one time, and those users can then decide to reply.
Here's what I'm currently working with:
tbl_pm tbl:
id
date_sent
title
content
status ENUM ('unread', 'read') DEFAULT 'unread'
tblpm_info tbl:
id
message_id
sender_id
receiver_id
However, I need some help determining the logic on two things:
1) When a new message is created, should the "id" be auto-increment? If the 'id' column is set to auto-increment in both tables, how would I set the "message_id" column in the 'relation table'?
For example, when a new message is created, my MySQL statement is as follows:
<?php
mysql_query("INSERT INTO `tblpm` (title, content, sender_id, date_sent) VALUES ('$subject', '$message', '$sender', NOW())" );
In the same statement, how would I enter the 'auto-incremented' value of tblpm into the tblpm_info "message_id" field?
2) What should my MySQL statement look like when users reply to messages?
Perhaps I am making this more complicated than I need to. Any help is greatly appreciated!
1) Definetely yes, id's should be auto-autoincremented unless you provide a different means of a primary key which is unique. You get the id of the insert either with mysql_insert_id() or LAST_INSERT_ID() from mysql directly, so to post some connected info you can do either
mysql_query("INSERT INTO table1 ...")
$foreign_key=mysql_insert_id(); //this gives you the last auto-increment for YOUR connection
or, but only if you're absolutely sure no one else writes to the table in the mean time or have control over the transaction, after insert do:
$foreign_key=mysql_query("SELECT LAST_INSERT_ID()")
INSERT INTO table2 message_id=$foreign_key
or, without pulling the FK to php, all in one transaction (I also advice to wrap the SQL as a transaction too) with something like:
"INSERT INTO table1...; INSERT INTO table2 (message_id,...) VALUES(LAST_INSERT_ID(),...)"
Depending on your language and mysql libraries, you might not be able to issue the multi-query approach, so you're better off with using the first approach.
2) This can have so many approaches, depending on if you need to reply to all the recepients too (e.g. conference), reply in a thread/forum-like manner, whether the client-side can store the last retrieved message/id (e.g. in a cookie; also affecting whether you really need the "read" field).
The "private chat" approach is the easiest one, you then are probably better off either storing the message in one table and the from-to relationships into an other (and use JOINs on them), or simply re-populate the message in one table (since storage is cheap nowadays). So, the simplistic model would be one table:
table: message_body,from,to
$recepients=array(1,2,3..);
foreach($recepients as $recepient)
mysql_query("INSERT INTO table (...,message_body,from,to) VALUES(...,$from,$recepient)");
(duplicate the message etc, only the recepient changes)
or
message_table: id,when,message_body
to-from-table: id,msg_id,from,to
$recepients=array(1,2,3,...);
mysql_insert("INSERT INTO message_table (when,message_body) VALUES(NOW(),$body)");
$msg_id=mysql_insert_id();
foreach($recepients as $recepient)
mysql_query("INSERT INTO to-from-table (msg_id,from,to) VALUES($msg_id,$from,$recepient)");
(message inserted once, store the relations and FK for all recepients)
Each client then stores the last message_id he/she received (default to 0), and assume all previous messages already read):
"SELECT * FROM message WHERE from=$user_id OR to=$user_id WHERE $msg_id>$last_msg_id"
or we just take note of the last input time from the user and query any new messages from then on:
"SELECT * FROM message WHERE from=$user_id OR to=$user_id WHERE when>='".date('Y-m-d H:i:s',$last_input_time)."' "
If you need a more conference- or forum-tread-like approach, and need to keep track of who read the message or not, you may need to keep track of all the users involved.
Assuming there won't be hundred-something people in one "multi-user conference" I'd go with one table for messages and the "comma-separated and wrapped list" trick I use a lot for storing tags.
id autoincrement (again, no need for a separate message id)
your usual: sent_at, title (if you need one), content
sender (int)
recepients (I'd go with varchar or shorter versions of TEXT; whereas TEXT or BLOB gives you unlimited number of users but may have impact on performance)
readers (same as above)
The secret for recepients/readers field is to populate them comma-separated id list and wrap it in commas again (I'll dulge into why later).
So you'd have to collect ids of recepients into an array again, e.g. $recepients=array(2,3,5) and modify your insert:
"INSERT INTO table (sent_at,title,content,sender,recepients) VALUES(NOW(),'$title','$content',$sender_id,',".implode(',', $recepients).",')"
you get table rows like
... sender | recepients
... 1 | ,2, //single user message
... 1 | ,3,5, //multi user message
to select all messages for a user with the id of $user_id=2 you go with
SELECT * FROM table WHERE sender=$user_id OR INSTR(recepients, ',$user_id,')
Previously we wrapped the imploded list of recepients, e.g. '5,2,3' becomes ',5,2,3,' and INSTR here tells if ',2,' is contained somewhere as a substring - since seeking for just '2',',2' or '2,' could give you false positives on e.g. '234,56','1**,234','9,452,**89' accordingly - that's why we had to wrap the list in the first place.
When the user reads/receives his/her message, you append their id to the readers list like:
UPDATE table SET readers=CONCAT(',',TRIM(TRAILING ',' FROM readers),',$user_id,') WHERE id=${initial message_id here}
which results in:
... sender | recepients | readers
... 1 | ,2, | ,2,
... 1 | ,3,5, | ,3,5,2,
Or we now can modify the initial query adding a column "is_read" to state whether the user previously read the message or not:
SELECT * FROM table WHERE INSTR(recepients, ',$user_id,'),INSTR(readers, ',$user_id,') AS is_read
collect the message-ids from the result and update the "recepients" fields with one go
"UPDATE table SET readers=CONCAT(',',TRIM(TRAILING ',' FROM readers),',$user_id,') WHERE id IN (".implode(',' ,$received_msg_ids).")"
You should not rely on auto-increment on both IDs due to the possibility of two users posting two messages at nearly the same time. If the first script inserts data into the tbl_pm table, then the second script manages to execute both its tbl_pm and tblpm_info inserts before the first script completes its tblpm_info insert, the first script's two database inserts will have different IDs.
Aside from that, your database structure doesn't seem well organized for the task at hand. Assuming your messages could be very long, and sent to a very large number of users, it would be ideal to have the message content stored once, and for each recipient have unread status, read time, etc. For example:
CREATE TABLE `pm_data` (
`id` smallint(5) unsigned NOT NULL auto_increment,
`date_sent` timestamp NOT NULL,
`title` varchar(255)
`sender_id` smallint(5) unsigned,
`parent_message_id` smallint(5) unsigned,
`content` text,
PRIMARY_KEY (`id`)
);
CREATE TABLE `pm_info` (
`id` smallint(5) unsigned NOT NULL auto_increment,
`pm_id` smallint(5) unsigned NOT NULL,
`recipient_id` smallint(5) unsigned,
`read` tinyint(1) unsigned default 0,
`read_date` timestamp,
PRIMARY_KEY (`id`)
);
Create these two tables, and notice both of them have an 'id' value set to auto-increment, but the 'info' table also has a pm_id field that would hold the ID number of the 'data' row that it refers to, such that you're sure each row has a primary key in the 'info' table that you can use to select from.
If you want a true relational database setup using MySQL, make sure your engine is set to InnoDB, which allows relationships to be set up between tables, so (for example) if you try to insert something into the 'info' table that refers to a pm_id that doesn't exist in the 'data' table, the INSERT will fail.
Once you've chosen a database structure, then your PHP code would look something like:
<?php
// Store these in variables such that if they change, you don't need to edit all your queries
$data_table = 'data_table';
$info_table = 'info_table';
mysql_query("INSERT INTO `$data_table` (title, content, sender_id, date_sent) VALUES ('$subject', '$message', '$sender', NOW())" );
$pmid = mysql_insert_id(); // Get the inserted ID
foreach ($recipent_list as $recipient) {
mysql_query("INSERT INTO `$info_table` (pm_id, recipient_id) VALUES ('$pmid', '$recipient')" );
}
Yes. You would definitely set auto_increment on both of the ids.
To set the message_id you would programatically insert it in there.
Your query would look like this:
mysql_query("INSERT INTO `tblpm` (title, content, sender_id, date_sent) VALUES ('$subject', '$message', '$sender', NOW())" );
Notice it's the same! If the id is set to auto_increment it will do all the magic for you.
In plain PHP/Mysql calls, mysql_insert_id() returns the auto-incremented value from the previous INSERT operation
So, you insert the message, collect the newly generated ID, and put that value into the other table.
Personally in your case (providing the example was not simplified and there is not more I cannot see) I would store the data from both of those table in a single table, as they appear to be directly related:
tbl_pm tbl:
message_id
date_sent
title
content
status ENUM ('unread', 'read') DEFAULT 'unread'
sender_id
receiver_id
So you end up with something like the above, there is not really any need for the join as the relationship is always going to be 1 to 1? You have read / unread in the tbl_pm table which would surely be changed per recipient, meaning you are having to store a copy of the message for each recipient anyway. perhaps staus is supposed to be in the tbl_pm info table.
If you do want to insert into both tables try using last_insert_id() within a query or
mysql_insert_id() as explained above, from within php.
I'd probably do something similar to what gavin recommended, but if you wanted threaded messages, you'd have to add another key, like this:
private_messages
- title (text)
- date (timestamp)
- content (text)
- status (enum)
- sender_id (int)
- receiver_id (int)
- parent_message_id (int)
Then you could have nested messages without a separate table or system.

Categories