Returning first result where CASE result = X? - mysql

I'm trying to return to write a query that only returns first result where someid (result of the first case)=X - and ignoring subsequent results where someid=X. How would I alter the following query to achieve that?
Here's what I'm currently using:
SELECT DISTINCT CASE WHEN $userid != senderid THEN senderid ELSE GROUP_CONCAT(receivers.id SEPARATOR ', ') END someid,
CASE WHEN $userid != senderid THEN senders.username ELSE GROUP_CONCAT(receivers.username SEPARATOR ', ') END somename,
messages.body,
messages.time
FROM messages
LEFT JOIN messages_recipients AS recipients ON messages.id = recipients.messageid
LEFT JOIN users AS senders ON messages.senderid = senders.id
LEFT JOIN users AS receivers ON recipients.userid = receivers.id
WHERE recipients.userid = $userid
OR messages.senderid = $userid
GROUP BY messages.id
ORDER BY messages.time DESC

Based on the information from your comments, I have taken another stab at this.
I'm very hands-on so although I can conceptualize what will happen, I didn't know for sure till I started tinkering around.
So, let's start with the data. Based on what we talked about, I created three tables:
users
id user_name
1 Walker
2 John
3 Kate
messages
id senderid body time
1 1 ignored 1 2010-04-01 00:00:00.000
2 1 ignored 2 2010-04-02 00:00:00.000
3 3 ignored 3 2010-04-03 00:00:00.000
4 1 msg A to john and kate 2010-04-10 00:00:00.000
5 3 msg b from kate to walker and john 2010-04-11 00:00:00.000
*messages_recipients*
id messageid userid
1 1 2
2 1 3
3 2 2
4 3 1
5 4 2
6 4 3
7 5 1
8 5 2
The data is tailored in such a way that you (Walker) have both sent and received messages through the month of April with John and Kate.
You can see a list of these messages by running the following sql statement:
SELECT
u2.user_name AS Sender,
u1.user_name AS Receiver,
m.body,
m.time
FROM
messages m
JOIN
messages_recipients mr ON m.id = mr.messageid
JOIN
users u1 ON mr.userid = u1.id
JOIN
users u2 ON m.senderid = u2.id
ORDER BY
time DESC
Now, that we have the test scenario, the tricky part: returning the most recently communicate message between you (Walker) and John and Kate. I've got a rather long SQL statement and, admittedly, I am not the best at creating these, but I think this will still work:
BEGIN
DECLARE #UserId INT = 1
--A. Main Query
SELECT
CASE
WHEN mtemp.senderid = 1 --#UserId
THEN
CONCAT('Message To: ', receivers.user_name)
ELSE
CONCAT('Message From: ' , senders.user_name)
END AS MessageType,
mtemp.body,
mtemp.time
FROM
messages mtemp
INNER JOIN users senders ON
mtemp.senderid = senders.id
INNER JOIN
(
--B. Inner Query determining most recent message (based on time)
-- between #UserID and the person #UserID
-- Communicated with (either as sender or receiver)
select userid,max(maxtime) as maxmaxtime from
(
--C.1. First part of Union Query Aggregating sent/received messages on passed #UserId
SELECT
m2.body,
kk.*
FROM
`messages` m2 INNER JOIN
(
SELECT DISTINCT
userid,
MAX(m.time) AS MaxTime
FROM
messages m INNER JOIN
messages_recipients mr ON m.id = mr.messageid AND
m.senderid = 1 --#UserId
GROUP BY
mr.userid
) kk on m2.time = kk.MaxTime and m2.senderid = 1 --#UserId
UNION
--C.2. Second part of Union Query Aggregating sent/received messages on passed #UserId
SELECT
m1.body,
jj.*
FROM
`messages` m1 INNER JOIN
----C.2a. Inner most query of users users who sent message to userid
(SELECT DISTINCT
senderid as userid,
MAX(m.time) AS MaxTime
FROM
messages m INNER JOIN
messages_recipients mr ON m.id = mr.messageid AND
mr.userid = 1 --#UserId
GROUP BY
m.senderid) jj on m1.time = jj.MaxTime and m1.senderid = jj.userid
) MaximumUserTime
group by
MaximumUserTime.userid
) AggregatedData on mtemp.time = AggregatedData.maxmaxtime
INNER JOIN users receivers on AggregatedData.userid = receivers.id
ORDER BY `time` DESC
END
To test in phpMyAdmin, you'll have to remove the comments and the begin/end declare statements as well. I just wanted to post this as if it would look in a procedure.
The query assumes that you won't both send and receive a different message from the same user at the exact same time; the odds of that happening seem very small so I'm hoping may this will work.
When I run this query I get the following results:
MessageType body time
Message From: Kate msg b from kate to walker and john 2010-04-11 00:00:00.000
Message To: John msg A to john and kate 2010-04-10 00:00:00.000
That's the most recent communications concerning Walker among all those users who have communicated with Walker.
Hope that helps.

A sub-query may work in this case (though the performance of it may not make it a great option):
SELECT DISTINCT CASE WHEN $userid != senderid THEN senderid ELSE GROUP_CONCAT(receivers.id SEPARATOR ', ') END someid,
CASE WHEN $userid != senderid THEN senders.username ELSE GROUP_CONCAT(receivers.username SEPARATOR ', ') END somename,
messages.body,
messages.time
FROM messages
INNER JOIN
(
SELECT m.senderId, m.receiverId, MAX(m.time) AS MyMaxTime FROM messages GROUP BY m.senderId, m.receiverId
) myLittleQuery
ON messages.senderid = myLittleQuery.senderid AND messages.senderid = myLittleQuery.receiverId AND messages.time = myLittleQuery.MyMaxTime
LEFT JOIN messages_recipients AS recipients ON messages.id = recipients.messageid
LEFT JOIN users AS senders ON messages.senderid = senders.id
LEFT JOIN users AS receivers ON recipients.userid = receivers.id
WHERE recipients.userid = $userid
OR messages.senderid = $userid
GROUP BY messages.id
ORDER BY messages.time DESC
Been a while since I've been in the MySQL realm so my syntax is most likely off. However, the gist of it is there.
Also, depending on what you're trying to retrieve, you might be able to ditch the DISTINCT and CASE statements as well.

Related

MYSQL LEFT JOINing the latest record from another table

I have a training_stats table (current due training) and I also have a completed_training table.
What I want to do is query due training with the last completed date from the completed table.
I've nearly got what I want, I get the due training, but they are duplicated with each completed record(as there are many completed records to each current due), and I only want single rows and the latest completed date.
I've been trying to use MAX, and when I run the MAX query independently, I get the last record. But when the MAX query is in the join, it is returning all completed rows.
This is the query that I am using:
SELECT s.course_stat_id
,o.org_name
,u.id
,u.first_name
,u.last_name
,a.area_id
,a.area_name
,tc.course_id
,tc.course_name
,s.assigned_on
,s.due
,s.pass_mark
,s.completed_on
,completed.complete_training_id
,completed.complete_date
FROM training_stats s
JOIN organisations o ON o.org_id = s.org_id
LEFT JOIN (
SELECT complete_training_id
,user_id
,area_id
,course_id
,max(completed_on) AS complete_date
FROM completed_training
GROUP BY complete_training_id
) completed ON completed.user_id = s.user_id
AND completed.area_id = s.area_id
AND completed.course_id = s.course_id
LEFT JOIN users u ON u.id = s.user_id
LEFT JOIN areas a ON a.area_id = s.area_id
LEFT JOIN training_courses tc ON tc.course_id = s.course_id
WHERE u.active = 1
AND o.active = 1
AND s.assigned = 1
Can you see what I am doing wrong?
Not exactly positive of your expected results, but the failure is PROBABLY for your group by and JOIN. Your group by is ONLY on the training ID, but you are also pulling user, area and course as well as max date completed for said respective training ID, user, area, course. You group by and join should match the unique characteristics.
Without seeing data, the query as I interpret it is that the "complete_training_id" is an auto-increment column for that table. Having said that, there would only ever be one record for that ID.
Having said that, the completed training table can have for a single user, area and course, multiple training days of which you want the most recent. For example someone attending college and needs to take many computer classes and they are refreshers from prior so assume all are same course ID. A person could take in 2012, 2014, 2016. You would want the instance of the user/area/course showing the 2016 dated training. So lets look at that first.
select
ct.user_id,
ct.area_id,
ct.course_id,
max(ct.completed_on) AS complete_date
FROM
completed_training ct
GROUP BY
ct.user_id,
ct.area_id,
ct.course_id
Now, for each user, area and course of study, I have one record with the most recent completion date. NOW lets pull the rest of the details, but since you need the completed training ID too, I applied the MAX() of that in the query below. The ID should by default be increasing every time a new record is added, so one completed a year ago would have a lower value than the ID completed today. So you get both the completed ID and its corresponding date for a given user, area, course.
SELECT
s.course_stat_id,
o.org_name,
u.id,
u.first_name,
u.last_name,
a.area_id,
a.area_name,
tc.course_id,
tc.course_name,
s.assigned_on,
s.due,
s.pass_mark,
s.completed_on,
ct.complete_training_id,
ct.complete_date
FROM
training_stats s
JOIN organisations o
ON s.org_id = o.org_id
AND o.active = 1
LEFT JOIN
( select
ct.user_id,
ct.area_id,
ct.course_id,
max(ct.complete_training_id ) as complete_training_id,
max(ct.completed_on) AS complete_date
FROM
completed_training ct
GROUP BY
ct.user_id,
ct.area_id,
ct.course_id ) ct
on s.user_id = ct.user_id
AND s.area_id = ct.area_id
AND s.course_id = ct.course_id
JOIN users u
ON s.user_id = u.id
AND u.active = 1
LEFT JOIN areas a
ON s.area_id = a.area_id
LEFT JOIN training_courses tc
ON s.course_id = tc.course_id
WHERE
s.assigned = 1
I'm not 100% sure of that. First, run this query. It should list all completed training, with a rnk from 1 (lastest), to n (oldest).
SELECT complete_training_id
,user_id
,area_id
,course_id
,completed_on AS complete_date
,#curRank := case when complete_training_id <> #cur_complete_training_id then 0 else #curRank + 1 end rnk
FROM completed_training, (select #curRank := 0, #cur_complete_training_id := 0)
ORDER BY complete_training_id, completed_on DESC
If true, the answer is :
SELECT s.course_stat_id
,o.org_name
,u.id
,u.first_name
,u.last_name
,a.area_id
,a.area_name
,tc.course_id
,tc.course_name
,s.assigned_on
,s.due
,s.pass_mark
,s.completed_on
,completed.complete_training_id
,completed.complete_date
FROM training_stats s
JOIN organisations o ON o.org_id = s.org_id
LEFT JOIN (
SELECT complete_training_id
,user_id
,area_id
,course_id
,completed_on AS complete_date
,#curRank := case when complete_training_id <> #cur_complete_training_id then 0 else #curRank + 1 end rnk
FROM completed_training, (select #curRank := 0, #cur_complete_training_id := 0)
ORDER BY complete_training_id, completed_on DESC
) completed ON completed.user_id = s.user_id and completed.rnk = 1
AND completed.area_id = s.area_id
AND completed.course_id = s.course_id
LEFT JOIN users u ON u.id = s.user_id
LEFT JOIN areas a ON a.area_id = s.area_id
LEFT JOIN training_courses tc ON tc.course_id = s.course_id
WHERE u.active = 1
AND o.active = 1
AND s.assigned = 1

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
);

(My)SQL JOIN - get teams with exactly specified members

Assume tables
team: id, title
team_user: id_team, id_user
I'd like to select teams with just and only specified members. In this example I want team(s) where the only users are those with id 1 and 5, noone else. I came up with this SQL, but it seems to be a little overkill for such simple task.
SELECT team.*, COUNT(`team_user`.id_user) AS cnt
FROM `team`
JOIN `team_user` user0 ON `user0`.id_team = `team`.id AND `user0`.id_user = 1
JOIN `team_user` user1 ON `user1`.id_team = `team`.id AND `user1`.id_user = 5
JOIN `team_user` ON `team_user`.id_team = `team`.id
GROUP BY `team`.id
HAVING cnt = 2
EDIT: Thank you all for your help. If you want to actually try your ideas, you can use example database structure and data found here: http://down.lipe.cz/team_members.sql
How about
SELECT *
FROM team t
JOIN team_user tu ON (tu.id_team = t.id)
GROUP BY t.id
HAVING (SUM(tu.id_user IN (1,5)) = 2) AND (SUM(tu.id_user NOT IN (1,5)) = 0)
I'm assuming a unique index on team_user(id_team, id_user).
You can use
SELECT
DISTINCT id,
COUNT(tu.id_user) as cnt
FROM
team t
JOIN team_user tu ON ( tu.id_team = t.id )
GROUP BY
t.id
HAVING
count(tu.user_id) = count( CASE WHEN tu.user_id = 1 or tu.user_id = 5 THEN 1 ELSE 0 END )
AND cnt = 2
Not sure why you'd need the cnt = 2 condition, the query would get only those teams where all of users having the ID of either 1 or 5
Try This
SELECT team.*, COUNT(`team_user`.id_user) AS cnt FROM `team`
JOIN `team_user` ON `team_user`.id_team = `team`.id
where `team_user`.id_user IN (1,5)
GROUP BY `team`.id
HAVING cnt = 2

Get conversation and last message Query

i'm tring to get the conversations with the relative last message and order them by time and if is read the message. Let's go to show the my logic.
I created 3 table: inbox_join / inbox_msg / users
On the first table "inbox join" i have the datas about who have a active discussion. In this case we have id_user - "1" and id_user_2 - "4" they have a conversation.
On the inbox_msg table I have the text message, id conversation where the message will shown and other field easy to understand.
Inbox join table
Inbox_msg table
Users table
I made a query that work fine, but the my issue is that i can't have the occured_at on the inbox_msg table. I would like find a better solution for have my desidered result and i can't order how i'm looking for.
This is my query
SELECT DISTINCT (
inbox_join.id_conversation
), user_chat.name AS name_conv, user_chat.surname AS surname_conv, user_chat.username as username_conv, user_chat.id as id_chat, image_upload.name_image, (
SELECT DISTINCT (
message
)
FROM inbox_msg
WHERE inbox_join.id_conversation = inbox_msg.id_conversation
ORDER BY occured_at DESC
LIMIT 1
) AS last_msg, users.name, users.surname
FROM inbox_join
INNER JOIN users ON users.id = inbox_join.id_user
INNER JOIN users AS user_chat ON user_chat.id <> 1 AND (inbox_join.id_user_2 = user_chat.id || inbox_join.id_user = user_chat.id)
INNER JOIN image_upload ON image_upload.id_image = user_chat.profile_image
WHERE inbox_join.id_user = 1
OR inbox_join.id_user_2 = 1
Result desidered selecting the conversation about user 1:
id_conversation | id_user | name | surname | username | last_msg | occured_at_last_msg | read_msg |
1 4 E S E Yes 1380724676 0
4 5 G E K Good 1380724675 0
Query:
SELECT im.id_conversation,
im.id_user,
u.name,
u.surname,
u.username,
im.message AS last_msg,
im.occured_at AS occured_at_last_msg,
im.read_msg
FROM inbox_msg im
JOIN users u
ON u.id_user = im.id_user
JOIN (SELECT id_conversation,
MAX(occured_at) AS occured_at
FROM inbox_msg
GROUP BY id_conversation) im2
ON im2.id_conversation = im.id_conversation
AND im2.occured_at = im.occured_at
ORDER BY im.occured_at DESC
I made this query and should work fine, I would like receive comment about this query.
SELECT DISTINCT (
im.id_conversation
), users.name, users.surname, users.username, image_upload.name_image, im.message as last_msg, im.occured_at, im.read_msg
FROM inbox_join
INNER JOIN (
SELECT sub . *
FROM (
SELECT DISTINCT (
id_conversation
), id_user, message, occured_at, read_msg
FROM inbox_msg
WHERE id_user <> 1
ORDER BY occured_at DESC
) AS sub
GROUP BY sub.id_user
ORDER BY sub.occured_at DESC
) AS im ON im.id_conversation = im.id_conversation
INNER JOIN users ON im.id_user = users.id
INNER JOIN image_upload ON users.profile_image = image_upload.id_image
WHERE inbox_join.id_user = 1 || inbox_join.id_user_2 = 1

Returning Multiple Null Values Using Outer Join

I'm trying to get an SQL query to return NULL values using a LEFT OUTER JOIN between my two tables.
One table contains user information, and the other contains roll call information. Essentially, I'm trying to generate an HTML table to display whether or not someone signed a roll call for each month. If the value is NULL, it means they were not a member at the time of that roll call.
The problem I'm having right now is that the query will only return one NULL value, even if they weren't a member for more than one roll call. Here's the query I'm using where the user was not a member for more than one roll call:
SELECT m.userid, r.status, r.rollcallid
FROM fleetmgr_members AS m
LEFT OUTER JOIN fleetmgr_rollcall_log AS r
ON m.userid = r.userid AND r.rollcallid IN(1, 5)
WHERE m.userid = 2089
ORDER BY m.currentName, r.rollcallid;
This query returns:
userid status rollcallid
2089 NULL NULL
I want it to return:
userid status rollcallid
2089 NULL 1
2089 NULL 5
For some odd reason, achieving what I want it to return has totally stumped me - so thanks in advance for any assistance you can provide.
fleetmgr_rollcall_log Table:
id rollcallid userid date_signed status activity comments
2 1 652 1330563886 signed 6-8 hrs/week -
29 1 2462 1330565521 signed 9-11 hrs/week -
1823 5 731 1333300321 signed 0-2 hrs/week -
2293 5 166 1333901983 exempt 0-2 hrs/week -
fleetmgr_members Table:
id userid currentName
1 3 SomeUsername
2 5 DifferentUsername
How about something like this
SELECT r1.rollcallid, r2.status
FROM (
SELECT DISTINCT rollcallid
FROM fleetmgr_rollcall_log
) AS r1
LEFT JOIN fleetmgr_rollcall_log AS r2
ON r1.rollcallid=r2.rollcallid AND r2.userid=2089
ORDER BY r1.rollcallid;
I've not returned any user data as the only user data you've got in your sample results is the user_id which you know already as it's in your query.
Move the r.rollcallid IN(1, 5)to the where clause, not the join clause.
Try this
SELECT m.userid, r.status, r.rollcallid
FROM fleetmgr_members m, fleetmgr_rollcall_log r
WHERE m.userid = 2089 AND r.rollcallid IN(1, 5)
ORDER BY m.currentName, r.rollcallid;
So what about this (using CROSS JOIN):
SELECT m.userid, r.status, r.rollcallid
FROM fleetmgr_members m cross join fleetmgr_rollcall_log r
WHERE m.userid = 2089 AND r.rollcallid IN(1, 5)
ORDER BY m.currentName, r.rollcallid;
Why this doesn't work?
SELECT m.userid, r.status, r.rollcallid
FROM fleetmgr_members AS m
LEFT OUTER JOIN fleetmgr_rollcall_log AS r
ON m.userid = r.userid
WHERE m.userid = 2089 AND r.rollcallid IN(1, 5)
ORDER BY m.currentName, r.rollcallid;