MySQL join with two potential field names - mysql

For a website I'm working on I was tasked with implementing a private messaging system.
My basic scenario here is, there are several message entries in the database, each containing a sender and a recipient. However, the "current user" should be able to see both those messages, as they are both relevant to him.
The problem is, I am only interested in the data of the other user, not my own. But the "current user" can be both the sender or the recipient.
My query down here gets this job done, but it is hardly elegant. I am joining both users, then deciding using an IF which data I should get.
SELECT
IF(m.sender = ?, 1, 0) AS isself,
IF(m.sender = ?, u_recipient.id, u_sender.id) AS other_id,
IF(m.sender = ?, u_recipient.displayName, u_sender.displayName) AS other_name,
IF(m.sender = ?, u_recipient_avatar.url, u_sender_avatar.url) AS other_avatar,
m.text AS text
FROM messages AS m
LEFT JOIN user AS u_sender
ON u_sender.id = m.sender
LEFT JOIN avatars AS u_sender_avatar
ON u_sender_avatar.id = u_sender.avatarId
LEFT JOIN user AS u_recipient
ON u_recipient.id = m.recipient
LEFT JOIN avatars AS u_recipient_avatar
ON u_recipient_avatar.id = u_recipient.avatarId
WHERE ( m.sender = ? OR m.recipient = ? )
AND UNIX_TIMESTAMP(m.timestamp) > ?
ORDER BY m.timestamp ASC
LIMIT 100
So basically, my question here is, is there any more elegant way of doing this? Storing the sender/recipient int into 1 single table to be reused in the join? Otherwise, is this a performance hog (joining tables I don't need?). Or should I just take care of seperating these in the application itself?
Thanks in advance!

Seeing that I am not allowed to edit the other answer, which was partially correct.
My answer here is based on Ben's, however, with the syntax errors removed.
SELECT d.isself, other_id, u.displayName AS other_name, a.url AS other_avatar, text
FROM
(
SELECT 0 AS isself, m.sender AS other_id, m.timestamp, m.text
FROM messages AS m
WHERE m.recipient = ?
UNION
SELECT 1 AS isself, m.recipient AS other_id, m.timestamp, m.text
FROM messages AS m
WHERE m.sender = ?
) AS d
LEFT JOIN user AS u
ON u.id = d.other_id
LEFT JOIN avatars AS a
ON a.id = u.avatarId
WHERE UNIX_TIMESTAMP(timestamp) > ?
ORDER BY m.timestamp ASC
LIMIT 100

How about something like:
SELECT isself, other_id, u.displayName AS other_name, a.url AS other_avatar, text
FROM
(
SELECT 0 AS isself, m.sender AS other_id, m.timestamp, m.text
FROM messages AS m
WHERE m.recipient = ?
UNION
SELECT 1 AS isself, m.recipient AS other_id, m.timestamp, m.text
FROM messages AS m
WHERE m.sender = ?
) AS d
LEFT JOIN user AS u
ON u.id = d.other_id
LEFT JOIN avatars AS a
ON a.id = u.avatarId
WHERE UNIX_TIMESTAMP(timestamp) > ?
LIMIT 100
If the message's sender and recipient IDs will always be found in the user table, the "LEFT JOIN user" should be changed to an inner join--"JOIN user". If each user has an avatar entry, then that left join should also be changed to an inner join.

Related

How to select sql data by using 2 join tables with OR condition without duplicate records

I have a mysql select statement as below:-
select user.username, chat.from_user,chat.to_user,chat.message,chat.date_created
from chat join user on (chat.from_user or chat.to_user) in (user.user_id)
where(chat.from_user = 3 or chat.to_user = 3) and chat.chat_id IN
(SELECT distinct (MAX(chat.chat_id) )
FROM chat GROUP BY chat_group_id);
and here is my result
I always get username = admin. My expectation result is username will get correct from / to user.
Please help. Thank you.
SELECT
IF(chat.from_user=3, to_user
, IF(chat.to_user=3, form_user, 0)) AS username,
chat.from_user,chat.to_user,chat.message,chat.date_created
FROM chat
LEFT JOIN user fr_user ON chat.from_user = user.user_id
LEFT JOIN user to_user ON chat.to_user = user.user_id -- since you only want to show the TO_USERNAME, you can remove above line
WHERE (chat.from_user = 3 OR chat.to_user = 3) and chat.chat_id
IN
(SELECT distinct (MAX(chat.chat_id) )
FROM chat GROUP BY chat_group_id);
I think you intend:
select u.username, c.from_user, c.to_user, chat.message, c.date_created
from chat c join
user u
on u.user_id in (c.from_user, c.to_user)
where 3 in (c.from_user, c.to_user) and
c.chat_id in (select max(c2.chat_id)
from chat c2
group by chat_group_id
);

Query inside NOT IN statement Doctrine

I found this sentence in the code:
$dql
= <<<DQL
SELECT u FROM AppBundle:User u
JOIN u.Roles r
JOIN u.team t
WHERE u.id NOT IN (
SELECT user.id
FROM GameBundle:Goal g
JOIN g.user user
WHERE
g.objective = :objective
)
AND
r.profile = :sales_profile
AND
r.company = :company
AND
u.onlyStatus NOT IN (:status)
DQL;
I don't know how to works that query inside NOT IN sentence, help me please.
I need to know:
What return the query inside of NOT IN, (data types, so on...)
How to works a query inside of NOT IN (is posible ?)
SELECT u FROM AppBundle:User u
JOIN u.Roles r
JOIN u.team t
WHERE u.id NOT IN (
SELECT user.id
FROM GameBundle:Goal g
JOIN g.user user
WHERE
g.objective = :objective
)
this means take all the users that don't currently have 'objective' in the 'Goal' table.
Is this what you needed ?

Why does a LEFT JOIN break my MySQL query?

SELECT value AS $point_fld, COUNT(value) as cnt
FROM ?_data d LEFT JOIN
?_user u
ON d.`table` = 'user' AND d.`id` = u.user_id
WHERE `key` = ? AND `value`<>'' AND `value`<>'Blank' AND
u.added BETWEEN ? AND ?
/* ----- I added this bit */
LEFT JOIN ?_invoice_payment p
ON d.'id' = p.user_id
SUM(p.amount) as moneysum
/* End ---- */
GROUP BY $point_fld
I am trying to edit this query to add in a sum value for payments based on which users are returned in the first part. The commented text is what I added. It is working just as intended but when I add in my part to get the values it breaks it.
Any suggestions would be amazing thanks. I'm pretty new to joins and stuff in SQL.
SELECT value AS $point_fld,
COUNT(value) as cnt,
SUM(p.amount) as moneysum
FROM ?_data d
INNER JOIN ?_user u ON d.`table` = 'user' AND d.`id` = u.user_id
LEFT JOIN ?_invoice_payment p ON d.'id' = p.user_id
WHERE `key` = ? AND `value`<>'' AND `value`<>'Blank' AND
u.added BETWEEN ? AND ?
GROUP BY value
OK this is what finally worked. Thanks for the help!
SELECT value AS $point_fld,
COUNT(DISTINCT u.user_id) as cnt,
SUM(p.amount) as moneysum
FROM ?_data d
LEFT JOIN ?_user u ON d.table = 'user' AND d.id = u.user_id
LEFT JOIN ?_invoice_payment p ON d.id = p.user_id
WHERE key = ? AND value<>'' AND value<>'Blank' AND
u.added BETWEEN ? AND ?
GROUP BY value

MySQL GROUP BY / ORDER BY issue with flat messages table / threads

Ok, I'm trying to base something similar off of this, but not quite getting it nailed: GROUP BY and ORDER BY
Basically, wanting a query to find the latest messages in each 'thread' between the current logged-in user and any other users, but via a flat (non-'threaded') table of messages:
messages {
id,
from_uid,
to_uid,
message_text,
time_added
}
Assuming the current user's uid is '1', and the latest message in each 'thread' could either be from that user, or to that user (the other party always denoted by thread_recipient):
SELECT a.*,thread_recipient
FROM messages a
JOIN (SELECT IF(from_uid = '1',to_uid,from_uid) AS thread_recipient,
MAX(time_added) AS recency
FROM messages
WHERE (from_uid = '1' OR to_uid = '1')
GROUP BY thread_recipient) b ON thread_recipient = (IF(a.from_uid = '1',a.to_uid,a.from_uid))
AND b.recency = a.time_added
ORDER BY a.time_added DESC
But I fear this ain't gonna work right, and maybe messages sent at the same time might end up being returned for the wrong user?
Is my WHERE condition misplaced?
Any wisdom much appreciated.
Here's an idea: take the UNION of the following two queries, then get
the maximum dates from the result.
SELECT id,to_uid AS other_party,time_added FROM messages WHERE from_uid = '1'
SELECT id,from_uid AS other_party,time_added FROM messages WHERE to_uid = '1'
When you do the following:
SELECT MAX(time_added),other_party
FROM (SELECT id,to_uid AS other_party,time_added FROM messages WHERE from_uid = '1'
UNION
SELECT id,from_uid AS other_party,time_added FROM messages WHERE to_uid = '1'
) MyMessages
GROUP BY other_party
You will get the most recent time associated with a message sent to
each person that user '1' is corresponding with. Then you can join the
results of that to the original messages table to get what you want:
SELECT Messages.*
FROM (SELECT MAX(time_added) AS MaxTime,other_party
FROM (SELECT id,to_uid AS other_party,time_added FROM messages WHERE from_uid = '1'
UNION
SELECT id,from_uid AS other_party,time_added FROM messages WHERE to_uid = '1'
) MyMessages
GROUP BY other_party
)
JOIN Messages
ON (Messages.time_added = MyMessages.MaxTime AND
(Messages.to_uid = MyMessages.other_party AND Messages.from_uid = '1' OR
Messages.from_uid = MyMessages.other_party AND Messages.to_uid = '1')
)
Try this - I think it gives you what you are looking for more simply and efficiently.
SELECT latest_time = MAX(a.time_added, b.time_added)
FROM messages a, messages b
LEFT JOIN messages a1
ON a1.from_uid = a.from_id
-- find the case where the following doesn't exist
-- so you know there is nothing after b1
LEFT JOIN messages a2
ON a2.from_uid = a.from_id
AND a2.time_added > a1.time_added
LEFT JOIN messages b1
ON b1.to_uid = b.to_id
LEFT JOIN messages b2
ON b2.to_uid = b.to_id
AND b2.time_added > b1.time_added
WHERE a.from_id = '1'
AND b.to_id = '1'
AND c1.id IS NULL
AND c2.id IS NULL
ORDER BY a.time_added DESC
Ok, after comments - if id is autoincrement use it instead of message time. But you have propoer condition to ensure that messages will not be delivered to wrong persons.

Updating users table from complex SQL query, users.id not recognised?

So the other day, I asked this question about how to combine three complex queries and found a way to do it. Now I'm trying to use those queries to update a field in the users table and can't find a way to make it work. Here's the query:
update users set field_sum =(
select sum(field_sum) from (
select sum(field_one) as field_sum
from t_a join t_b on (t_a.bid = t_b.id) where t_b.user_id=users.id
union all
select sum(field_two) as field_sum
from t_c join t_d on (t_c.did = t_d.id) where t_d.user_id=users.id
union all
select sum(field_three) as field_sum
from t_e where t_e.user_id=users.id
) as field_sumT
)
When I try to run it, I get the following error: ERROR 1054 (42S22): Unknown column 'users.id' in 'where clause'. When I try removing the .user_id=users.id bit from each where clause, it will run but ends up with the total sum of field_sum, not just the field_sum for that user. Is there any way to accomplish this?
Use:
UPDATE USERS u
LEFT JOIN (SELECT t_b.user_id,
SUM(field_one) as field_sum
FROM t_a
JOIN t_b on t_a.bid = t_b.id
GROUP BY t_b.user_id) a ON a.user_id = u.id
LEFT JOIN (SELECT t_d.user_id,
SUM(field_two) as field_sum
FROM t_c
JOIN t_d on t_c.did = t_d.id
GROUP BY t_d.user_id) b ON b.user_id = u.id
LEFT JOIN (SELECT t_e.user_id,
SUM(field_three) as field_sum
from t_e
GROUP BY t_e.user_id) c ON c.user_id = u.id
SET field_num = COALESCE(a.field_sum, 0) + COALESCE(b.field_sum, 0) + COALESCE(c.field_sum, 0)
Caveat
This will set any users with no records in the supporting rows to have a field_sum value of zero. Do you only want to update those with a record in at least one of those tables?