Facebook Style Messages: Efficient Query - mysql

users tbl:
id
username
first_name
last_name
email_address
messages tbl:
id
date_sent
title
content
status
message_relation tbl:
id
message_id
sender_id
receiver_id
What would be the most efficient way to query a message TO a selected user given these tables? In other words, I want to list all messages that are in userA's "inbox"
Secondly, how would you recommend handling global messages that need to be sent to everyone from the admin?

I think this is what you're looking for..
SELECT [WHATEVER] FROM
messages
INNER JOIN message_relation
ON (messages.id = message_relation.message_id AND message_relation.receiver_id = $id)
This does what you asked for efficiently. If you also want to select fields on either of the users, you can JOIN Receiver_ID to User_ID to to get that.
Suggested tables setup if you want Global/Admin messages:
Messages (Message_ID, [Fields common to all messages, e.g. Message_Content, Message_Timestamp, etc])
Global_Messages (Global_Message_ID, Message_ID, [any fields specific to Global_Messages])
User_Messages (User_Message_ID, Message_ID, [any fields specific to User_Messages])
User_Message_Relations (User_Message_Relations_ID, User_Message_ID, Sender_ID, Receiver_ID)
Then, to query an Inbox, something like:
SELECT [WHATEVER] FROM
Messages
LEFT JOIN (Global_Messages)
ON (Messages.Message_ID = Global_Messages.Message_ID)
LEFT JOIN (User_Messages)
ON (Messages.Message_ID = User_Messages.Message_ID)
LEFT JOIN (User_Message_Relations)
ON (User_Messages.User_Message_ID = User_Message_Relations.User_Message_ID AND User_Message_Relations.Receiver_ID = $uid)
That will give you a result you can loop through to get a user's entire Inbox.

SELECT
m.id, m.date_sent,m.title,m.content,m.status
FROM
messages m
INNER JOIN
message_relation mr on (mr.message_id = m.id)
INNER JOIN
users u on (u.id = mr.receiver_id);
WHERE u.id = ...your current user id ...
This is a simple way of doing it, and it will be fast and almost no load on your database

Related

conversation type inbox messages between two users for a website

I want to achieve a conversation type inbox messages between two users in which the conversations will be group between two users and displayed on the inbox page and when clicked on the conversation the overall chat history will be displayed between two users ....
I have a database schema but failed to group the conversations between two users.I need a query for acheiving this Thank you
here are my tables.
1.Users:
id
username
password
2.conversations:
conversation_id
from_user
to_user
3.messages:
message_id
conversation_id
user_id
message_text
message_date
seen
so if someone know how to achieve this query then help me also changes to my schema will be appreciated thank you
SELECT conversation FROM
conversations
WHERE (conversations.from_user = <user parameter>
OR conversations.to_user = <user parameter>)
AND conversations.from_user <> conversations.to_user
You can get all conversations of current user with last message by conversation using query that looks like the following:
SELECT users.id,
users.name,
messages.conversation_id,
messages.message_id,
messages.message_text,
messages.message_date,
messages.seen
FROM
(SELECT conversations.conversation_id,
IF(conversations.from_user = <current_user_id>, conversations.to_user, conversations.from_user) AS interlocutor_id,
max(messages.message_id) AS last_message_id
FROM conversations
LEFT JOIN messages ON conversations.conversation_id = messages.conversation_id
WHERE convesations.from_id = <current_user_id>
OR conversations.to_id = <current_user_id>
GROUP BY conversations.conversation_id) AS t
LEFT JOIN users ON users.id = t.intercolutor_id
LEFT JOIN messages ON messages.message_id = t.last_message_id
If you need get all conversations of current user with last indox messages using:
SELECT users.id,
users.name,
messages.conversation_id,
messages.message_id,
messages.message_text,
messages.message_date,
messages.seen
FROM
(SELECT conversations.conversation_id,
IF(conversations.from_user = <current_user_id>, conversations.to_user, conversations.from_user) AS interlocutor_id,
max(messages.message_id) AS last_message_id
FROM conversations
LEFT JOIN messages ON conversations.conversation_id = messages.conversation_id
WHERE (convesations.from_id = <current_user_id>
OR conversations.to_id = <current_user_id>)
AND messages.user_id <> <current_user_id>
GROUP BY conversations.conversation_id) AS t
LEFT JOIN users ON users.id = t.intercolutor_id
LEFT JOIN messages ON messages.message_id = t.last_message_id
But I think than better way if you will rewrite your database scheme and you will add id of last message in 'conversations' table. It will make the query more optimal
UPDATED:
If you need get list of all users that have conversation with current user:
SELECT t.conversation_id,
users.id,
users.name
FROM
(SELECT conversation_id,
IF(from_user = <current_user_id>, to_user, from_user) AS interlocutor_id
FROM conversations
WHERE from_id = <current_user_id>
OR to_id = <current_user_id>) AS t
LEFT JOIN users ON users.id = t.intercolutor_id
P.S. You must replace <current_user_id> to actual indentifier of current user

mySQL SELECT help. IF or EXISTS?

I have this DB structure
* user
user_id
name
* client
client_id
name
* user_client
user_client_id
user_id
client_id
* message
message_id
client_id
description
If there are entries on user_client then the user has permissions restricted to the specific clients listed for his id on the table. If there are no entries, then the user has access to any client.
How can I select only messages that the user can read?
I'm trying to do an IF on the WHERE clause to check if any entries on the user_client table but I don't know where to go from there. It needs to select all messages from any client if no entries on user_client or only messages for client_id specified on user_client table
Thanks for the help!
I would suggest doing two different queries: one for the superusers and the other for the restricted users. Then you can join the two results with a UNION.
SELECT M.message_id,
M.client_id,
M.description
FROM message M
INNER JOIN user_client UC ON (UC.client_id = M.client_id)
INNER JOIN user U ON (UC.user_id = U.id)
WHERE U.id = :user_id
UNION
SELECT M.message_id,
M.client_id,
M.description
FROM message M
WHERE NOT EXISTS (
SELECT *
FROM user_client
WHERE user_id = :user_id
)
You can obtain the same result with other queries but IMHO this one is clearer and more maintainable.
Edit: If you want to ensure that the user exists you should join the second query with the user table.
SELECT M.message_id,
M.client_id,
M.description
FROM message M
JOIN user U
WHERE U.id = :user_id
AND NOT EXISTS (
SELECT *
FROM user_client
WHERE user_id = :user_id
)
One way the do this could be to use two different queries to create a set of the messages users can see and filter according to your needs; something like this should work:
select * from (
select u.user_id, u.name, c.name client, m.message_id, m.description
from user u
join user_client uc on u.user_id = uc.user_id
join client c on uc.client_id = c.client_id
join message m on c.client_id = m.client_id
union all
select u.user_id, u.name, c.name client, m.message_id, m.description
from user u
cross join client c
join message m on c.client_id = m.client_id
where user_id not in (select user_id from user_client)
) x
where x.user_id = 1;
Here users present in the user_client table are restricted to the set of messages that they have access to (the first set in the union), while users not present in the user_client table can see all messages (the second set in the union).
Sample SQL Fiddle
If I am understanding your question correctly, such as
1) Administrative user... They can look at EVERYTHING since they would have no records in the user_client table.
2) Client Supervisor... Such a person who's primary responsibility is to a specific client (or multiple clients). Therefore, the user DOES have a record in the user_client table. If so, then only allow the user to see records for those clients they DO have associations with.
select
m.message_id,
m.client_id,
m.description,
c.name as clientName
from
( select count(*) as HasClients
from user_client
where user_id = TheUserYouWant ) ClientCheck,
message m
left join user_client uc
on m.client_id = uc.client_id
AND uc.user_id = TheUserYouWant
join client c
on m.client_id = c.client_id
where
ClientCheck.HasClients = 0
OR NOT uc.user_id IS NULL
The query looks at the user_client table TWICE. The first time is to just get a count of those records that DO exist for the given user, regardless of which client associated with. The query will always come back with 1 row and it will either be 0 (no such records), or greater than 1 (however many they are associated with).
The second instance is a LEFT-JOIN to the user_client table, JUST IN CASE the person IS restricted to only looking at their own client messages.
The WHERE clause now comes in and says... if the underlying count of clients was zero, then ok to give me all messages. If any other value, then the user ID in the user_client table (as left-joined to the messages on both the CLIENT AND THE USER you want) MUST EXIST (via NOT a NULL value for the user_id column).
Now, you probably don't want to query EVERY message as it could get quite large as your database grows, but you could put whatever other criteria in the WHERE clause, such as date restrictions and/or client(s) you are interested in.

Select distinct and count other table

In a small chat service, two users have a common chat.
This SQL selects a list of the users that you have chatted with.
SELECT DISTINCT user.user_id, user.username
FROM user
INNER JOIN message
ON user.user_id = message.owner_user_id OR user.user_id = message.to_user_id
WHERE message.owner_user_id = :activeUserId
OR message.to_user_id = :activeUserId
ORDER BY message.date_time DESC
I also need to get the number of messages that has been sent between two users. The output is a list of "folders", one for each user, the active user has chatted with. Each folder contains the username of the user and the number of messages in the chat (the sum of the active user's number of messages to the specific user, and the specific user's number of messages to the active user).
row1: (active user and JohnSmith have 33 messsages in their common chat.)
user_id = 1;
username = 'JohnSmith';
message_count = 33;
row2: (active user and Johnny have 43 messsages in their common chat.)
user_id = 2;
username = 'Johnny';
message_count = 43;
How could this be done in one SQL-statement?
Right now you're "flattening" the results using DISTINCT. You can flatten and count using the COUNT(*) function and GROUP BY:
SELECT DISTINCT user.user_id, user.username, COUNT(*)
FROM user
INNER JOIN message
ON user.user_id = message.owner_user_id OR user.user_id = message.to_user_id
WHERE message.owner_user_id = :activeUserId OR message.to_user_id = :activeUserId
GROUP BY user.user_id, user.username
ORDER BY message.date_time DESC
The only changes to your query are the additional column (COUNT(*)) and the GROUP BY line.
Your question seems a bit ambiguous. It seems like you want to count the messages from one user to each other user they communicate with -- regardless of whether they are the "from" or "to" roles on the message.
When dealing with messages, one approach is to normalize them with the smaller user id first and then the larger user id. The result from such a query would be one row for your users, that looks like:
1 'JohnSmith' 2 'Johnny' 33
rather than two separate rows. You can get the two separate rows as well, but that seems less useful to me.
The query that produces that style of output is:
select least(m.owner_user_id, m.to_user_id) as uid1,
greatest(m.owner_user_id, m.to_user_id) as uid2,
u1.username, u2.username, count(*) as nummessages
from message m join
user u1
on u1.user_id = least(m.owner_user_id, m.to_user_id) join
user u2
on u2.user_id = greatest(m.owner_user_id, m.to_user_id)
WHERE m.owner_user_id = :activeUserId or
m.to_user_id = :activeUserId
group by least(m.owner_user_id, m.to_user_id) as uid1,
greatest(m.owner_user_id, m.to_user_id);
Try:
SELECT DISTINCT(user.user_id), user.username, COUNT(c.to_user_id) AS message_count
FROM user
INNER JOIN message
ON user.user_id = message.owner_user_id
OR user.user_id = message.to_user_id
INNER JOIN message m1
ON user.user_id = m1.owner_user_id
OR user.user_id = m1.to_user_id
WHERE message.owner_user_id = :activeUserId
OR message.to_user_id = :activeUserId
ORDER BY message.date_time DESC

How to I grab the alternate object than the one I am searching with this inner join

I have two tables, a users table and a messages table.
users:
id (int)
username (varchar)
messages:
id(int)
send_id(int)
receive_id(int)
I am using this query which gets me all of the messages that a specific user sent:
SELECT users.id, users.username FROM users
INNER JOIN messages
ON messages.send_id = users.id
AND users.id = SOMEUSERID
How would I go about grabbing the receiving user's id and username instead of the sending users information?
SELECT
rec.id, rec.username
FROM
users send
JOIN messages ON (messages.send_id = send.id)
JOIN users rec ON (messages.receive_id = rec.id)
WHERE
send.id = SOMEUSERID

Need feedback on an SQL Request (MySQL, GROUP BY behavior)

I'm programming a private conversation system and need advices on my most complex request.
My (simplified) tables, under MySQL 5.5 :
user:
id (int)
username (string)
conversation:
id (int)
subject (string)
conversation_user:
conversation_id (int)
user_id (int)
message:
id (int)
conversation_id (int)
user_id (int)
content (text)
created_at (datetime)
So, I want to display to the user the list of the conversations in which he is participating : for each conversation, I need the conversation id, the conversation subject, the list of participating users, the last message id, the last message author id and username, and the last message date.
I've written this request, which looks pretty bad :
SELECT
cu.conversation_id,
c.subject,
u.username,
m.id,
m.user_id,
m.created_at
FROM conversation_user cu
JOIN conversation c ON c.id = cu.conversation_id
JOIN conversation_user cu2 ON cu2.conversation_id = c.conversation_id
JOIN user u ON u.id = cu2.user_id
JOIN message m ON m.conversation_id = cu.conversation_id
WHERE cu.user_id = :current_user_id # the session user_id
AND m.id = (SELECT MAX(id) FROM message WHERE conversation_id = cu.conversation_id)
So, I would like to know if you guys see a better way to get the same result ?
I've already read GROUP BY behavior when no aggregate functions are present in the SELECT clause, that's why I didn't put a GROUP BY clause and wrote the last line instead :
AND m.id = (SELECT MAX(id) FROM message WHERE conversation_id = cu.conversation_id)
Thanks !
Edit
I did an EXPLAIN on this request, and there is no KEY used for the JOIN user u ON u.id = cu2.user_id line : why ?
(the first question is still relevant)
There are alternatives, such as I'm about to put forward for you. But, honestly, that's how I'd do it :)
AND m.id = (SELECT id FROM message WHERE conversation_id = cu.conversation_id ORDER BY id DESC LIMIT 1)
The above allows you to order by DATE or somet other field, and still pick just one message id.
JOIN
message m
ON m.conversation_id = cu.conversation_id
JOIN
(SELECT conversation_id, MAX(id) AS id FROM message GROUP BY conversation_id) AS filter
ON filter.conversation_id = cu.conversation_id
AND filter.id = m.id
The above avoids a correlated sub-query, and so can (but not always) be faster.
In terms of there be no key being used for JOIN user u ON u.id = cu2.user_id, do both of the two tables in question have indexes on the relevant fields?