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?
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
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;
Related
I have 2 entities with a many to many relation. Users - Roles_Users - Roles
How can write a query that returns to me all the users that only has exactly one role which is the role name "customer". I wrote something like this:
SELECT `users`.* FROM `users`
INNER JOIN `roles_users` ON `roles_users`.`user_id` = `users`.`id`
INNER JOIN `roles` ON `roles`.`id` = `roles_users`.`role_id`
WHERE roles.name not in ('admin' , 'sac', 'superadmin', 'customer_service' , 'supplier');
but it still brought to me users that has more than the role 'customer'.
I need the users that has ONLY the role of 'customer' and nothing else
This is how I would solve it:
SELECT `users`.*
FROM `users`
WHERE id IN
(
SELECT `roles_users`.`user_id`
FROM `roles_users`
INNER JOIN `roles`
ON `roles`.`id` = `roles_users`.`role_id`
GROUP BY `roles_users`.`user_id`
HAVING COUNT(*) = 1 -- only a single role
AND MAX(roles.name) = 'customer' -- and this role is 'customer'
)
Btw, there's no need to use all those backticks:
SELECT *
FROM users
WHERE id IN
(
SELECT roles_users.user_id
FROM roles_users
JOIN roles
ON roles.id = roles_users.role_id
GROUP BY roles_users.user_id
HAVING COUNT(*) = 1 -- only a single role
AND MAX(roles.name) = 'customer' -- and this role is 'customer'
)
Isn't this easier to read (and write)?
Compare role name directly,because using in can get you other non required values
SELECT `users`.* FROM `users` INNER JOIN `roles_users` ON `roles_users`.`user_id` = `users`.`id` INNER JOIN `roles` ON `roles`.`id` = `roles_users`.`role_id` WHERE roles.name ='customer'
Why not just look users with the specific role?
SELECT `users`.* FROM `users`
JOIN `roles_users` ON `roles_users`.`user_id` = `users`.`id`
JOIN `roles` ON `roles`.`id` = `roles_users`.`role_id`
WHERE `roles`.name = 'customer';
Use a NOT EXISTS subquery in the WHERE clause:
SELECT u.*
FROM users u
INNER JOIN roles_users ru ON ru.user_id = u.id
INNER JOIN roles r ON r.id = ru.role_id
WHERE r.name = 'customer'
AND NOT EXISTS (
SELECT *
FROM roles_users ru1
WHERE ru1.user_id = ru.user_id
AND ru1.role_id <> ru.role_id
)
The NOT EXISTS condition will ensure that no other roles are assigned to the user.
A shorter but slower solution could be:
SELECT u.*
FROM users u
INNER JOIN roles_users ru ON ru.user_id = u.id
INNER JOIN roles r ON r.id = ru.role_id
GROUP BY u.id
HAVING GROUP_CONCAT(r.name) = 'customer'
I have the following SQL query:
SELECT users.user_id,
users.first_name,
users.last_name,
roles.role,
roles.role_id,
users.username,
users.description,
users_vs_teams.team_id,
teams.team_name,
teams.status,
teams.notes
FROM teams
INNER JOIN users_vs_teams ON teams.team_id = users_vs_teams.team_id
RIGHT OUTER JOIN users ON users_vs_teams.user_id = users.user_id
INNER JOIN roles ON users.role_id = roles.role_id
WHERE( users.role_id = 3 ) AND ( teams.status = 'Completed' ) OR ( teams.status IS NULL )
I want to display only users with a role_id of 3 but team.status can be either Completed or NULL. However, this query displays all roles where teams.status is either Completed or NULL. Any help resolving this issue will be greatly appreciated.
First, I'm not sure if you need an outer join for this. Second, your problem seems to be parentheses in the WHERE clause:
SELECT u.user_id, u.first_name, u.last_name, r.role, r.role_id,
u.username, u.description, uvt.team_id,
t.team_name, t.status, t.notes
FROM teams t INNER JOIN
users_vs_teams uvt
ON t.team_id = uvt.team_id INNER JOIN
users u
ON uvt.user_id = u.user_id
roles r
ON u.role_id = r.role_id ON u
WHERE (u.role_id = 3) AND (t.status = 'Completed' OR t.status IS NULL)
Note that table aliases make the query easier to write and to read.
Remove the RIGHT OUTER JOIN and fix your parenthesis in your WHERE clause.
SELECT users.user_id,
users.first_name,
users.last_name,
roles.role,
roles.role_id,
users.username,
users.description,
users_vs_teams.team_id,
teams.team_name,
teams.status,
teams.notes
FROM teams
INNER JOIN users_vs_teams ON teams.team_id = users_vs_teams.team_id
INNER JOIN users ON users_vs_teams.user_id = users.user_id
INNER JOIN roles ON users.role_id = roles.role_id
WHERE( users.role_id = 3 ) AND ( teams.status = 'Completed' OR teams.status IS NULL)
you can also do something like this:
( teams.status = 'Completed' OR ISNULL(teams.status,'') = '')
I am trying to use the following code to get the 6 users with which the current user has most recently chatted. I have two problems. First of all, if the current user has recieved a message from the other user but has only sent, that other user isnt fetched. Second of all, the ORDER BY clause is causing an error. Im a beginner in SQL so I have no idea what's going on.
Thanks in Advance!
Here's the code:
SELECT users.*
FROM users INNER JOIN
messages fromuser
ON (fromuser.fromid = users.id) INNER JOIN
messages touser
ON (touser.toid = users.id)
WHERE fromuser.toid = :userid OR touser.fromid = :meid
GROUP BY users.id
ORDER BY MAX(messages.datetime)
LIMIT 6;
This should do your job, and it relies less on MySQL extensions than your other answer so far. I estimate that it would perform about the same, but it's surely wordier.
SELECT u.*
FROM (
SELECT DISTINCT otherid
FROM (
SELECT
m.fromid AS otherid,
MAX(m.datetime) as maxts
FROM messages m
WHERE m.toid = :userid
GROUP BY m.fromid
UNION ALL
SELECT
m.toid AS otherid,
MAX(m.datetime) as maxts
FROM messages m
WHERE m.fromid = :userid
GROUP BY m.toid
) um
ORDER BY maxts DESC
LIMIT 6
) otheru
INNER JOIN users u
ON u.id = otheru.otherid
Your logic is doomed to fail, because one users.id cannot be two different values at the same time. I think this query does what you want:
SELECT u.*
FROM messages m INNER JOIN
users u
ON (m.fromid = u.id AND m.toid = :userid) OR
(m.toid = u.id AND m.fromid = :userid )
GROUP BY u.id
ORDER BY MAX(m.datetime) DESC
LIMIT 6;
Notice that it joins to the users table by the id that is not the current user.
I have a table of messages:
id
sender_id
recipient_id
I want to pull all the messages of user (id:1) where he was either the sender or the recipient and JOIN the "other guy" he was talking to.
something like:
SELECT * FROM `message`
LEFT JOIN `user` AS u ON IF(sender_id != 1, sender_id = u.id, recipient_id = u.id)
WHERE sender_id=1 OR recipient_id=1
the reason I don't join both the sender and recipient is that I already have the user I'm searching on, so I think it's a waste to join him on every message - unless you tell me otherwise!
This query I got from another SO question, but I've read it's not efficient at all, so what will be a better query?
This would work:
SELECT *
FROM message
JOIN users ON (message.sender_id = 1 AND user.id = message.recipient_id)
OR (message.recipient_id = 1 AND user.id = message.sender_id)
Note that there are various other ways of doing this in a more performant fashion. This is the most straightforward method that I can see.
Your way could work but the join won't use an index. If you want a fast join you need something like joined_table_indexed_field = some_expression, for example:
SELECT *
FROM `message` m
JOIN `user` AS u ON u.id = IF(m.sender_id = 1, m.recipient_id , m.sender_id)
WHERE m.sender_id=1 OR m.recipient_id=1
In this case #Grim's solution is better because it doesn't rely on mysql to perform an union index_merge for the where clause.
You could try a union:
(
SELECT * FROM `message`
INNER JOIN `user` u ON u.id = recipient_id
WHERE sender_id = 1
) UNION (
SELECT * FROM `message`
INNER JOIN `user` u ON u.id = sender_id
WHERE recipient_id = 1
)
I have 3 MySQL tables namely chat_comments, chat_friends and user_details and I want to display a friend list.
My tables:
chat_comments(comment_id,comment,user_id,user_id2,date_added)
chat_friends(user_id,user_id2,approved)
user_details(user_id, mainimage_id, fullname)
To do this, I need a query that will return the needed fields (u.mainimage_id, u.fullname, b.comment, b.user_id) so I can loop through the list to display a table.
SQL so far (help from #Andriy M):
SELECT
cc.comment,
cc.date_added,
u.fullname,
u.mainimage_id
FROM
user_details u
LEFT JOIN
chat_comments cc
INNER JOIN (
SELECT
user_id,
MAX(comment_id) AS maxcomment
FROM chat_comments WHERE user_id=2020 OR user_id2=2020
GROUP BY user_id
) a ON a.user_id = cc.user_id
AND a.maxcomment = cc.comment_id
ON a.user_id = u.user_id
WHERE u.user_id IN (
SELECT user_id2
FROM chat_friends
WHERE user_id = 2020
AND approved = 1
)
The above query returns the last comment made by the logged-in user's friends in conversation not the last comment between the logged-in user and his/her friend regardless of who made it.
I would like it to return the last comment between the logged-in user and their friend individually regardless of who made it. In the chat_messages table, user_id is the sender and user_id2 is the receiver. Hope it makes sense?
Like #imm said in a comment, you need to use an outer join. In case of a left join, the user_details table should become the left side of the join, the right side being the result of your inner join of chat_comments with your a derived table. You'll also need to remove the user_id IN (…) condition from inside the a subselect and re-apply it to the user_details table. Here:
SELECT
cc.comment,
cc.date_added,
u.fullname,
u.mainimage_id
FROM
user_details u
LEFT JOIN
chat_comments cc
INNER JOIN (
SELECT
user_id,
MAX(comment_id) AS maxcomment
FROM chat_comments
GROUP BY user_id
) a ON a.user_id = cc.user_id
AND a.maxcomment = cc.comment_id
ON a.user_id = u.user_id
WHERE u.user_id IN (
SELECT user_id2
FROM chat_friends
WHERE user_id = 2020
AND approved = 1
)
;
Alternatively, you could use a right join. In this case you would just need to move the user_id IN (…) condition, similarly to the LEFT JOIN solution above, and replace the second INNER JOIN with RIGHT JOIN:
SELECT
cc.comment, cc.date_added, u.fullname, u.mainimage_id
FROM
(
SELECT user_id, MAX(comment_id) AS maxcomment
FROM chat_comments
GROUP BY user_id
) a
INNER JOIN
chat_comments cc ON
a.user_id = cc.user_id AND
a.maxcomment = cc.comment_id
RIGHT JOIN
user_details u ON
a.user_id = u.user_id
WHERE u.user_id IN (select user_id2 from chat_friends where user_id=2020 AND approved=1)