1

I have a table called last_msg, in there i store the last mensage from a private chat between two users, and i update the column from and to when I send a new mensage. I use this table to show a list of mensages like facebook. I also use this table to another things, so i would rather fix the problem described as bellow.

Because of the ON users.user_id = last_msg.from i get data only from who is sending the mensage, this was the best i got... This is my current sql:

SELECT `last_msg`.`msg`, `last_msg`.`from`, `users`.`username`, `users`.`avatar`
FROM `last_msg` 
    INNER JOIN `users` 
        ON `users`.`user_id` = `last_msg`.`from` 
        WHERE `last_msg`.`to` = :user_id_logged OR `last_msg`.`from` = :user_id_logged_2

On the INNER JOIN users I want to get data only from the other user that i'm talking to in the chat, and the data from last_msg can be from both sender and receiver, as the facebook does.

So i tried:

SELECT `last_msg`.`msg`, `last_msg`.`from`, `users`.`username`, `users`.`avatar` 
FROM `last_msg` 
    INNER JOIN `users` 
        ON `users`.`user_id` != :user_logged 
        WHERE (`last_msg`.`to` = :user_logged_2 OR `last_msg`.`from` = :user_logged_3)

But it did not work, it's returning a list of all users in the table users. Any suggestions about how can i fix it?

mario
  • 367
  • 1
  • 4
  • 17
  • MySQL is a little rusty but I think you want to select on the user table and then join it with the last message table. – BugSquasher Apr 11 '19 at 03:26
  • 2
    can you put screenshots of table data and also the output you expect. – user2648008 Apr 11 '19 at 03:58
  • On a side note: I would avoid names that are SQL keywords like `from`. This forces you to always add ugly backticks to your columns. Moreover, what is `from`? A name? An ID? Better names would be `to_user_id` or `sender_user_id` or `user_id_sender` or the like, showing this is a user ID referencing a row in the users table. – Thorsten Kettner Apr 11 '19 at 08:28

2 Answers2

1

You can try joining the users table two times, one for the from user and the other for the to user like shown below, also note you need to use LEFT Join in order to get all from and to users.

SELECT `last_msg`.`msg`, `last_msg`.`from`, `FromUser`.`username`, `FromUser`.`avatar`,`ToUser`.`username`,`ToUser`.`avatar`
FROM `last_msg` 
    LEFT JOIN `users` as `FromUser` 
        ON `FromUser`.`user_id` = `last_msg`.`from` 
    LEFT JOIN `users` as `ToUser` 
        ON `ToUser`.`user_id` = `last_msg`.`to` 
        WHERE `last_msg`.`to` = :user_id_logged OR `last_msg`.`from` = :user_id_logged_2";

Updated Code

SELECT `last_msg`.`msg`, `last_msg`.`from`, `users`.`username`, `users`.`avatar` 
FROM `last_msg` 
INNER JOIN `users` 
ON `users`.`user_id` =`last_msg`.`to` 
WHERE `last_msg`.`from`= :user_logged 

UNION 

SELECT `last_msg`.`msg`, `last_msg`.`to`, `users`.`username`, `users`.`avatar` 
FROM `last_msg` 
INNER JOIN `users` 
ON `users`.`user_id` =`last_msg`.`from` 
WHERE `last_msg`.`to`= :user_logged 
Ajan Balakumaran
  • 1,639
  • 1
  • 8
  • 16
  • this query is working the same as my first snippet if i change `ON users.user_id = last_msg.from ` to `ON users.user_id = last_msg.to` – mario Apr 11 '19 at 06:09
  • Hi can you summarize what you need as output, if logged user is same as from user what you want? and when logged user is not the from user then what you want? – Ajan Balakumaran Apr 11 '19 at 06:19
  • It's better if you can give some sample data and the desired output – Ajan Balakumaran Apr 11 '19 at 06:20
  • As a example. on facebook, [image](https://www.dummies.com/wp-content/uploads/442108.image0.jpg) on the left side you can see a list of mensages, the avatar and username that is showed is always from the other user, but the mensage can be from both sender and receiver. I have this data in two different tables, `last_msg`, in this table i store the `msg`, `to`, `from`, and on the table `users` i store the `username` and `avatar`, on the table `users` i want to get only the `avatar` and `username` that is not equal to the logged user. Did you got it? – mario Apr 11 '19 at 07:06
  • Correct me If I'm wrong, so what you need is for a given user(logged user) you want all the other users whom he has chat with? – Ajan Balakumaran Apr 11 '19 at 07:11
  • I saw your updated code, it's returning data from the table `users` only from the logged user (i want the opposite of this), that's because of the `WHERE` clause from both queries. I tried to change a bit the query, but i'm not finding a way to get the desired result, does is that even possible? haha – mario Apr 11 '19 at 08:56
1

This is how to get the messages last sent to you:

SELECT msg, `from` as other_user_id
FROM last_msg
WHERE `to` = :user_logged;

If you also want the messages you last sent:

SELECT msg, `to` as other_user_id
FROM last_msg
WHERE `from` = :user_logged;

You say you store only the one last message per chat. I read this as for users A and B there will only be one row in the table (either the last message A sent to B or the last message B sent to A). So you can just combine the two queries and still show only one last message per chat.

SELECT m.msg, u.user_id, u.username, u.avatar
FROM
(
  SELECT msg, `from` as other_user_id
  FROM last_msg
  WHERE `to` = :user_logged
  UNION ALL
  SELECT msg, `to` as other_user_id
  FROM last_msg
  WHERE `from` = :user_logged
) m
JOIN users u ON u.user_id = m.other_user_id;

An alternative to UNION ALL is a join and CASE WHEN:

SELECT m.msg, u.user_id, u.username, u.avatar
FROM last_msg m
JOIN users u
  ON u.user_id = CASE WHEN :user_logged = m.from THEN m.to ELSE m.from END as other_user_id
WHERE :user_logged IN (m.from, m.to)

This ON clause can also be written as

  ON (u.user_id = m.from AND :user_logged = m.to)
  OR (u.user_id = m.to AND :user_logged = m.from)

or

ON u.user_id IN (m.from, m.to) AND u.user_id <> :user_logged;

by the way. Pick what you like better.

And here is a way to use the user ID parameter only once in the query:

SELECT m.msg, u.user_id, u.username, u.avatar
FROM (select :user_logged as user_id) myself
JOIN last_msg m ON myself.user_id IN (m.from, m.to)
JOIN users u ON u.user_id IN (m.from, m.to) AND u.user_id <> myself.user_id;
Thorsten Kettner
  • 89,309
  • 7
  • 49
  • 73
  • Correct me If I'm wrong, according to OP's scenario Union is Ok right? or is it necessary to include Union all? – Ajan Balakumaran Apr 11 '19 at 08:46
  • 1
    @Ajan Balakumaran: Yes, `UNION` would work here, too. But the question "is it necessary to include `UNION ALL`?" is misleading. `UNION ALL` is the simple command that merely glues two data sets. `UNION DISTINCT` (or `UNION` for short) is the more complicated command. It glues two data sets plus it removes duplicates. So the question should rather be: "Is it necessary to apply `UNION` here?" and the answer would be: "No, it's not. It suffices to apply `UNION ALL`". – Thorsten Kettner Apr 11 '19 at 09:42
  • Thank you for the prompt reply. I can see UNION ALL is little overhead as it's not removing duplicates. kudos – Ajan Balakumaran Apr 11 '19 at 09:45
  • 1
    @Ajan Balakumaran: It seems you have misunderstood. `UNION ALL` has no overhead. `UNION` does. Imagine I have two decks of playing cards, one only containing the Hearts cards, the other only containing number cards (i.e. no Jacks, Queens, etc.). Now you ask me either to give you all cards or you ask me to give you all cards but remove duplicate cards before, so as to get each card (e.g. the Seven of Hearts) just once. Which of the two tasks is more work for me? – Thorsten Kettner Apr 11 '19 at 10:16
  • Sorry for any misunderstanding what I meant was UNION ALL is less work than UNION hence when it comes to a point either would do it's better to go with UNION ALL. I understand overhead is not the right term to interpret that. – Ajan Balakumaran Apr 11 '19 at 10:23
  • 1
    @Ajan Balakumaran: Ah, okay, you meant "less" overhead. Sorry for the misunderstanding :-) – Thorsten Kettner Apr 11 '19 at 10:54