Join mysql tables twice on 2 columns = 1 column - mysql

I have a database that contains messages. The messages are stored in one table, the user information is stored in another. In the message table, there is an author_id column which represents the user_id of the author from the user table, there are all the message columns, and there is a to_address which represents a concatenation of "u_" + user_id from the user table. Is there any that I can join these two tables, so that it display the username instead of ID in BOTH the author_id AND to_address.
I've tried
SELECT username, ..., username
FROM msgs
INNER JOIN users
ON user_id=author_id AND concat("u_",user_id)=to_address;
with obvious error
I've tried using subqueries such as
SELECT
( SELECT username
FROM users
INNER JOIN msgs
ON user_id=author_id
) AS "From",
( SELECT username
FROM users
INNER JOIN msgs
ON CONCAT("u_",user_id)=to_address
) AS "To",
( SELECT timestamp(message_time) FROM msgs
) AS "Sent",
( SELECT message_subject FROM msgs
) AS "Subject",
( SELECT message_text AS "Message" FROM msgs
) AS "Message"
and got "Subquery returns more than 1 row". Is there any way that I can do this successfully?

It sounds like you want something like this:
SELECT
from_user.username AS "From",
to_user.username AS "To",
timestamp(msgs.message_time) AS "Sent",
msgs.message_subject AS "Subject",
msgs.message_text AS "Message"
FROM msgs
INNER JOIN users AS from_user
ON msgs.author_id = from_user.user_id
INNER JOIN users AS to_user
ON msgs.to_address = CONCAT("u_", to_user.user_id);
Basically, you join the users table to the msgs table twice, giving each instance of the table a different name and a different join condition. Then you can pick a specific column out of a specific instance of the users table.

I think you want to do something like
SELECT msgs.*,
authors.whatever,
addresses.to_address
FROM msgs
JOIN users AS authors ON msgs.author_id = authors.id
JOIN users AS addresses ON msgs.address_id = addresses.id
My query is perhaps imprecise but you can probably see what I'm doing here.
As an aside, I would recommend not abbreviating msgs and using singular table names.

You need two joins as you want to get two separate users:
select f.username, t.username
from msgs m
inner join users f on f.user_id = m.author_id
inner join users t on concat("u_", t.user_id) = m.to_address

This will return the username associated with both the "author_id" and the "to_address", using correlated subqueries, instead of using JOIN. (Using a JOIN is the usual approach, but an approach using a correlated subquery gives you some additional flexibility.
SELECT (SELECT u.username
FROM users u
ON u.user_id = CONCAT("u_",u.user_id) = m.to_address
ORDER BY u.username LIMIT 1
) AS to_username
, (SELECT a.username
FROM users a
ON a.user_id = m.author_id
ORDER BY a.username LIMIT 1
) AS author_username
, m.*
FROM msgs m
NOTE: this differs a bit from an INNER JOIN in that this will return a row from msg when a matching username is not found for the to_address or the author_id.)
NOTE: this assumes that user_id is unique in the users table.
NOTE: if the username column is NOT NULL in the users table, then you can emulate the INNER JOIN, and NOT return a row if a matching username is not found for the author_id or to_address by adding
HAVING to_username IS NOT NULL
AND author_username IS NOT NULL

Related

SELECT the last message of conversation - MySQL

I have query which looks like:
SELECT * FROM
( SELECT DISTINCT CASE
WHEN user1_id = 1
THEN user2_id
ELSE user1_id
END userID,conversationId
FROM conversations
WHERE 1 IN (user2_id,user1_id))dt
INNER JOIN users on dt.userID = users.id
It returns conversationId and information about user from users table. I would like to also add the last message (the one with biggest messageId) from message table on base of conversationId. The last thing would be to sort all the results by messageId
I tried to use another INNER JOIN which looked like :
INNER JOIN message on dt.conversationId = message.conversationId
Its adding messages to the result but I would like to get only the last one (the one with highest messageId as mentioned). I guess I would have to implement MAX somehow but I dont have idea how. The same thing with sorting all result by messageId so results with the biggest messageId would be first.
Thanks for all suggestions.
You can get the highest messageId for the conversation in a corelated subquery and use it for your join condition:
INNER JOIN message m
on m.conversationId = dt.conversationId
and m.messageId = (
SELECT MAX(m1.messageId)
FROM message m1
WHERE m1.conversationId = dt.conversationId
)
So the solution for eveything was following query
SELECT * FROM
( SELECT DISTINCT
CASE
WHEN user1_id = 1
THEN user2_id
ELSE user1_id
END userID,conversationId
FROM conversations
WHERE 1 IN (user2_id,user1_id))dt
INNER JOIN users on dt.userID = users.id
INNER JOIN message m on m.conversationId = dt.conversationId and m.messageId = (SELECT MAX(m1.messageId)
FROM message m1 WHERE m1.conversationId = dt.conversationId)
ORDER by m.messageId DESC

How to show username from another table on the basis of max id in sqql

I am trapped in a sql query, I know it may be common but not getting any proper solution.
From my table messages, I have successfully fetch max id by joining from and to columns, now what I am trying to do is, I want to pull the name of that max id from another table naming users,
Here is my working query to find max id,
select m.*
from messages m
where m.id in (select max(m.id) as max_id
from messages m
where m.`from` = 7
or m.`to` = 7
group by least(m.`to`, m.`from`), greatest(m.`to`, m.`from`))
I have tried something like this but it matches name with from column of messages table.
select messages.*, users.name
from messages
left join users on messages.`from` = users.id
where messages.id in (select max(id) as max_id
from messages
where `from` = 7
or `to` = 7
group by least(`to`, `from`), greatest(`to`, `from`))
I want that name will be shown according to max id that I am getting.
columns of messages table : id , from, to, created_at
columns of users table : id, name, email, created_at
Please help me out,
One method is to join, but only to the column that is not 7:
select m.*, u.name
from messages m join
users u
on u.id in (m.`from`, m.`to`) and u.id <> 7
where m.id in (select max(m.id) as max_id
from messages m
where 7 in (m.`from`, m.`to`)
group by least(m.`to`, m.`from`), greatest(m.`to`, m.`from`)
);

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.

Find unique values that do not exist in multiple columns and tables

A misconfigured manual import imported our entire AD into our help desk user database, creating a bunch of extraneous/duplicate accounts. Of course, no backup to restore from.
To facilitate the cleanup, I want to run a query that will find users not currently linked to any current or archived tickets. I have three tables, USER, HD_TICKET, and HD_ARCHIVE_TICKET. I want to compare the ID field in USER to the OWNER_ID and SUBMITTER_ID fields in the other two tables, returning the only the values in USER.ID that do not exist in any of the other four columns.
How can this be accomplished?
Do a left join for each relationship where the right table id is null:
select user.*
from user
left join hd_ticket on user.id = hd_ticket.owner_id
left join hd_ticket as hd_ticket2 on user.id = hd_ticket2.submitter_id
left join hd_archive_ticket on user.id = hd_archive_ticket.owner_id
left join hd_archive_ticket as hd_archive_ticket2 on user.id = hd_archive_ticket2.submitter_id
where hd_ticket.owner_id is null
and hd_ticket2.submitter_id is null
and hd_archive_ticket.owner_id is null
and hd_archive_ticket2.submitter_id is null
How about something like:
SELECT id
FROM user
WHERE id NOT IN
(
SELECT owner_id
FROM hd_ticket
UNION ALL
SELECT submitter_id
FROM hd_ticket
UNION ALL
SELECT owner_id
FROM hd_archive_ticket
UNION ALL
SELECT submitter_id
FROM hd_archive_ticket
)
If I understood you situation I would do this:
SELECT a.id FROM user a, hd_ticket b, hd_archive_ticket c WHERE a.id != b.id AND a.id != c.id
You would want to try something like below. Inner query where I am doing Inner join with other 2 tables, will return only those user id which exist in all 3 tables. Then in your outer query I am just filtering out those ID's returned by inner query; since your goal is to get only those USER ID which is not present in other tables.
select ID
FROM USER
WHERE ID NOT IN
(
select u.ID
from user u
inner join HD_TICKET h on u.ID = h.OWNER_ID
inner join HD_ARCHIVE_TICKET ha on u.ID = ha.SUBMITTER_ID
)

Complex? MySQL Join SELECT query

I'm trying to get a list of 'contacts' for a specified user id.
Let says my user id is 1, i need to get the list of ids of my my contacts from chat-contactlist then get all the infos for each id.
All users' id, name and contact information
Table usr: uid, rname, phonenumber
Online status and other stuff
Table chat-usr: uid, nickname, online_status
Containing user id and the user id of each contact this user have :
Table chat-contactlist: uid, cid (cid = The id of the person who's int he "uid" user list
So I need the name, the nickname, the online_status for all the 'cid' for a specified 'uid'... Dont know i read a tutorial about left join but it seams complex to merge multiple tables, anyone wanna try? Any recommendation?
Thank you
EDIT
Changing name by rname because name is a reserved word for SQL.
This is a 3-table join where you have a many-to-many relationship defined by the linker table chat-contactlist:
SELECT u.name username, c.nickname chat_username, c.online_status
FROM chat-contactlist cc
JOIN usr u ON (u.uid = cc.uid)
JOIN chat-usr c ON (c.uid = cc.cid);
This is an explicit join. If you are looking to select a specific user, you would add that in WHERE clauses afterward.
SELECT u.name username, c.nickname chat_username, c.online_status
FROM usr u, chat-usr c, chat-contactlist cc
WHERE cc.uid = u.uid
AND cc.cid = c.uid;
This is an implicit join, where the tables are related by equality in WHERE statements. It isn't recommended to use but I find them sometimes easier to read.
More info:
Basic many-to-many left join query
Explicit vs Implicit Joins
SELECT ccl.uid,
Name = u.Name,
Nickname = cu.nickname,
OnlineStatus = cu.onlinestatus
FROM chat-contactlist ccl
JOIN chat-usr cu ON ccl.cid = cu.uid
JOIN usr u ON u.uid = cu.uid
Where ccl.uid = #uid /* your filter here */