I'm creating a new chat system with ColdFusion, but I'm having a lot of trouble with sorting out a little problem.
First of all I'll explain how the system works. When creating a new chat with someone, it creates a conversation row in the MySQL table conversations. After that, users can send each other messages. When sending a message, a row is inserted into the table conversations_messages with the timestamp, the user that sent the message, the message, and of course the id of the message. Sounds logical, but now comes the big problem: organizing all those messages.
Users can view their messages with others on a page, this is the code of that page:
// Check conversation ID based on page url
<cfif IsDefined('URL.chat') and URL.chat neq "">
// Load conversation based on page url
<cfquery name = "getconv" datasource = "#DSN#">
SELECT *
FROM `conversations`
</cfquery>
// Get all messages from this conversation
<cfoutput query="getconv">
<cfquery name = "getmessages" datasource = "#DSN#">
SELECT *
FROM `conversations_messages`
WHERE `conversation` = '#chat#'
ORDER BY `conversations_messages`.`id` DESC
</cfquery>
// Get messages sent by 'user_one'
<cfquery name = "my" datasource = "#DSN#">
SELECT *
FROM `conversations_messages`
WHERE `conversation` LIKE '#chat#'
AND `user` LIKE '#user.id#'
ORDER BY `conversations_messages`.`id` DESC
</cfquery>
// Get messages sent by 'user_two'
<cfquery name = "friend" datasource = "#DSN#">
SELECT *
FROM `conversations_messages`
WHERE `conversation` LIKE '#chat#'
AND `user` LIKE '#getconv.user_two#'
</cfquery>
<div class="content">
// Messages sent by 'user_one' aka. user that created conversation
You: <cfoutput><cfloop query="my">#my.message#<br></cfloop></cfoutput>
// Messages sent by 'user_two'
Friend: <cfoutput><cfloop query="friend">#friend.message#</cfloop></cfoutput>
</cfoutput>
</div>
</cfif>
This is how the results show up right now: (as you can see it's sorted by user)
I want it to be sorted by message ID, so it shows up like this:
I hope it's a bit clearer now!
Help would be appreciated!
(not sure why everyone is answering in comments rather than as "an answer"?)
The order that your results are returned from the DB are purely down to the order you tell them to be returned. So if you want them chronologically, then don't fetch them ordered by name; order them chronologically.
So don't get user1's messages, and then user2's messages, just get the messages for the entire conversation in conversation order. Then when you're outputting them, look at which user made which comment and do the "you" / "friend" stuff.
That said, I think the approach you're taking is less than ideal. What you should perhaps be doing is fetching the messages incrementally, as they happen, rather than waiting to get all of them (if you see what I mean). So on each person's UI get all the messages since the last displayed message, and display 'em (in chronological order), then wait for some polling interval, and then fetch the next lot of messages that have occurred since the last time (etc).
If you're using ColdFusion 10 all this stuff has already been done for you via the Web Sockets technology CF has. So that would be the way to go if that's an option for you.
(as Adam suggested here is an answer)
It seems to me that your query getmessages has what you are after. It looks to be all messages from this conversation = chat ordered by conversations_messages.id.
You really shouldn't query for * either, specify the needed columns only. You will get better performance that way.
Also, always use <cfqueryparam> tags in your cfquery blocks.
Also, you cannot nest <cfoutput> tags without a group attribute. Your code is already in a <cfoutput> so no need for another.
If you are on ColdFusion 10 I would definitely give #Adam's answer a look.
Related
I'm having a quick issue I could use an opinion on. I'm attempting to write a MySQL query that pulls all the messages from a conversation that is exclusively between two users.
I need to be able to pull messages sent by 'user1' that are sent to 'user2', and messages sent by 'user2' that are sent to 'user1'.
I've dabbled a bit and I've currently got the 4 conditions needed for my query to execute (see below). I've been trying to work out what way to structure it to get the specific data I need though.
SELECT privatemessage_message, privatemessage_time_created, privatemessage_sent_by, privatemessage_sent_to
FROM tbl_privatemessages
WHERE privatemessage_sent_by = 1
OR privatemessage_sent_by = 2
OR privatemessage_sent_to = 1
OR privatemessage_sent_to = 2
ORDER BY privatemessage_time_created ASC
For context :
This is going to be used within a PHP MySQL website with AJAX being used for the private messaging. I'm fine withthem sides of it, but this particular SQL query is a nuisance.
Any help is greatly appreciated :)
You need to use AND to combine the sender and recipient, and OR to combine the different directions.
SELECT privatemessage_message, privatemessage_time_created, privatemessage_sent_by, privatemessage_sent_to
FROM tbl_privatemessages
WHERE (privatemessage_sent_by = 1 AND privatemessage_sent_to = 2)
OR (privatemessage_sent_to = 1 AND privatemessage_sent_by = 2)
ORDER BY privatemessage_time_created ASC
I would like to get the messages that someone hasnt read... it could be a count o just a "1" if there are pending messages to read.
The trick is that there are many " users" shareing the same system. So if I usear "A" reads a message from the table then the notification wont appear anymore to A, but for "B" there should be a notification of pending messages. They are sharing the same message lets say.
I create a query that works somehow , but I know is not 100% right.
I did review
Querying conversations from messages table
sql messages table query
In the example below is the deal.
"A" last viewbymessage for the docid 93 was on 2019-01-28 10:02:15, then user B send a new message BUT never reads the message sent by "A", so in my query, "A" will never be able to see there was a new message since he was the last to see if, and I not using the MessageTable only the Messages_View .. I know this is the wrong part, but im just stating how I used to have it.
SELECT B.*
FROM Comments_Viewed_Tbl B LEFT JOIN Comments_Viewed_Tbl C
ON (B.DOCID =C.DOCID and B.Date_Viewed < C.Date_Viewed)
WHERE C.Date_Viewed IS NULL and B.viewedby <>'A' and
B.RPDOC = 93 and B.Country ='USA'
*sorry for the image, I did try to put it as text but the system format irt ugly
How would be the best approach to do the query.
In this scenario A should have an alert or counter of the new message as also B since he/she didnt check it and just send a new one.
So adding a comment is the same as sending a message?
From my point of view, you need to add the CommentID column to the Comments_Viewed_Tbl, otherwise you will never be able to see the read status of each specific comment, only for the whole document.
Otherwise you will need to assume that the last person to add a comment to the document has read all previous comments.
I can't seem to figure this out since mysql is not my strong suit. I've done a bit of research here but can't put two and two together, so I'm turning to the community for some help.
I am building a chat function inside my Laravel app where two users can talk to each other. I can't figure out how to build the inbox (group the conversations together) portion of things. I've figured out how to get last message grouped by sending it.
The following is inside my User Model:
public function lastMessages() {
return $this->hasMany('App\Message', 'recipient_id')
->select('*')
->join(DB::raw('(Select max(id) as id from messages group by author_id) LatestMessage'), function($join) {
$join->on('messages.id', '=', 'LatestMessage.id');
})
->orderBy('created_at', 'desc');
}
My messages table consists of the following: author_id, recipient_id, messaged, viewed.
What I need to do is group messages, whether incoming our outgoing and display the latest message as inbox entry and once I click on that messages, the rest of the conversation pops up on the page.
My current sql shown above only gives me 1 last message, not the whole conversation.
Thanks in advance for all the help.
Firstly, I think you should keep the relationship function separate from logical(query) function.
If I understood your question correctly , you should do something like this :
Message::where('recipient_id',$userid)->orWhere('author_id',$userid)->orderBy('created_at','desc')->get();
This will give you messages send or received by user. Please clarify your question with Author and Message models if this is not what you want.
I'm making an app for selling books but I'm struggling with this part of it.
When a user wants to buy a book he can send a message to the book's seller. I have a table called conversations and another table called messages, which holds the messages for a conversation.
I have a page where a user can see a list of conversations he's involved. Conversations with the latest messages should be shown first. I solved this already but I also want to show the number of unread messages next to each conversation (messages have a "read" boolean field). I haven't been able to add the count as part of my original query; I know I could make a query for each item in the conversations collection but that would be inefficient as hell.
This is my query so far with the QueryBuilder:
$conversations = DB::table('conversations')
->select([
'books.title as bookTitle', 'conversations.id',
DB::raw('max(messages.created_at) as lastMessage'),
// DB::raw('(select count(id) from messages')
])
->where('conversations.from_user', $this->user->id)
->orWhere('conversations.to_user', $this->user->id)
->join('books', 'conversations.book_id', '=', 'books.id')
->join('messages', 'conversations.id', '=', 'messages.conversation_id')
->groupBy('messages.conversation_id')
->orderBy('lastMessage', 'DESC')
->get();
If i add the part commented out to attempt to get a field with the number of unread messages for that conversation I get a SQL syntax error. I also been thinking if maybe is not possible to add the count field since it might be exclusive to my original query: I sort the conversations taking into account all messages (read or unread) and pick the latest message for each conversation; the count field I want to add should only count unread messages.
Any ideas? Hope I explained myself.
Thanks.
It seems that instead of:
DB::raw('(select count(id) from messages')
you should simply use:
DB::raw('(select count(id) from messages) as messageCount')
You were missing here )
I have a site with about 30,000 members to which I'm adding a functionality that involves sending a random message from a pool of 40 possible messages. Members can never receive the same message twice.
One table contains the 40 messages and another table maps the many-to-many relationship between messages and members.
A cron script runs daily, selects a member from the 30,000, selects a message from the 40 and then checks to see if this message has been sent to this user before. If not, it sends the message. If yes, it runs the query again until it finds a message that has not yet been received by this member.
What I'm worried about now is that this m-m table will become very big: at 30,000 members and 40 messages we already have 1.2 million rows through which we have to search to find a message that has not yet been sent.
Is this a case for denormalisation? In the members table I could add 40 columns (message_1, message_2 ... message_40) in which a 1 flag is added each time a message is sent. If I'm not mistaken, this would make the queries in the cron script run much faster
?
I know that doesn't answer your original question, but wouldn't it be way faster if you selected all the messages that weren't yet sent to a user and then select one of those randomly?
See this pseudo-mysql here:
SELECT
CONCAT_WS(',', messages.ids) unsent_messages,
user.id user
FROM
messages,
user
WHERE
messages.id NOT IN (
SELECT
id
FROM
sent_messages
WHERE
user.id = sent_messages.user
)
GROUP BY ids
You could also append the id of the sent messages to a varchar-field in the members-table.
Despite of good manners, this would make it easily possible to use one statement to get a message that has not been sent yet for a specific member.
Just like this (if you surround the ids with '-')
SELECT message.id
FROM member, message
WHERE member.id = 2321
AND member.sentmessages NOT LIKE '%-' && id && '-%'
1.2 M rows # 8 bytes (+ overhead) per row is not a lot. It's so small I wouldn't even bet it needs indexing (but of course you should do it).
Normalization reduces redundancy and it is what you'll do if you have large amount of data which seems to be your case. You need not denormalize. Let there be an M-to-M table between members and messages.
You can archive the old data as your M-to-M data increases. I don't even see any conflicts because your cron job runs daily for this task and accounts only for the data for the current day. So you can archive M-to-M table data every week.
I believe there will be maintenance issue if you denormalize by adding additional coloumns to members table. I don't recommend the same. Archiving of old data can save you from trouble.
You could store only available (unsent) messages. This implies extra maintenance when you add or remove members or message types (nothing that can't be automated with foreign keys and triggers) but simplifies delivery: pick a random line from each user, send the message and remove the line. Also, your database will get smaller as messages get sent ;-)
You can achieve the effect of sending random messages by preallocating the random string in your m-m table and a pointer to the offset of the last message sent.
In more detail, create a table MemberMessages with columns
memberId,
messageIdList char(80) or varchar ,
lastMessage int,
primary key is memberId.
Pseudo-code for the cron job then looks like this...
ONE. Select next message for a member. If no row exists in MemberMessages for this member, go to step TWO. The sql to select next message looks like
select substr(messageIdList, 2*lastMessage + 1, 2) as nextMessageId
from MemberMessages
where member_id = ?
send the message identified by nextMessageId
then update lastMessage incrementing by 1, unless you have reached 39 in which case reset it to zero.
update MemberMessages
set lastMessage = MOD(lastMessage + 1, 40)
where member_id = ?
TWO. Create a random list of messageIds as a String of couplets like 2117390740... This is your random list of message IDs as an 80 char String. Insert a row to MemberMessages for your member_id setting message_id_list to your 80 char String and set last_message to 1.
Send the message identified by the first couplet from the list to the member.
You can create a kind of queue / heap.
ReceivedMessages
UserId
MessageId
then:
Pick up a member and select message to send:
SELECT * FROM Messages WHERE MessageId NOT IN (SELECT MessageId FROM ReceivedMessages WHERE UserId = #UserId) LIMIT 1
then insert MessageId and UserId to ReceivedMessages
and do send logic here
I hope that helps.
There are potential easier ways to do this, depending on how random you want "random" to be.
Consider that at the beginning of the day you shuffle an array A, [0..39] which describes the order of the messages to be sent to users today.
Also, consider that you have at most 40 Cron jobs, which are used to send messages to the users. Given the Nth cron job, and ID the selected user ID, numeric, you can choose M, the index of the message to send:
M = (A[N] + ID) % 40.
This way, a given ID would not receive the same message twice in the same day (because A[N] would be different), and two randomly selected users have a 1/40 chance of receiving the same message. If you want more "randomness" you can potentially use multiple arrays.