I have created a messaging system for users which will allow users to send message to another.
For this I have created two tables.
conversation(conversation_id,user_id1,user_id2)
messages(message_id,conversation_id,sender_id,receiver_id,message,created_time)
If users are talking first time, a conversation_id will be created with user_id1(who initiate chat) and user_id2(to who user_id1 is sending message)
Messages table will contain all info related to message.
Now what I want is, to create a message summary page where logged in user can view his all conversation list between other users order by created_time, group by conversation_id.
Here are table data:
conversation_id | user_id1 | user_id2
1 100 103
2 101 103
3 103 102
message_id| conversation_id| sender_id| receiver_id| message | created_time
1 1 100 103 MSG A 2012-06-08 08:38:57
2 1 103 100 MSG B 2012-06-08 08:39:40
3 2 101 103 MSG C 2012-06-08 08:40:20
4 3 102 103 MSG D 2012-06-08 08:41:10
And here is what output what I am looking for: Lets say logged in user is with id: 103
conversation_id| conversation_with | last_message | created_time
3 102 MSG D 2012-06-08 08:41:10
2 101 MSG C 2012-06-08 08:40:20
1 100 MSG B 2012-06-08 08:39:40
So this output is ordering by created_time, Grouping by conversation_id and displaying id of user in conversation_with , with who userid 103 is having conversation.
Can somebody provide MySQL query I will need to get this output.
This was a fun one. So, you have three main things to solve.
Alternate the conversation_with column depending on where the desired user shows up amongst two columns.
greatest-n-per-group to find the most recent message.
least-n-per-group to find the earliest message created_time.
The first one I solved by querying twice, alternating the user_id column, unioning the results, and then ordering the unioned results by created_time. I think I can solve this with one query and no unions, but it works for now.
2 and 3 are a little more involved. Here's a SQL fiddle with the query: http://sqlfiddle.com/#!2/bf2b7/1
TL;DNR
select * from (
select c.conversation_id, c.user_id2 as conversation_with, m1.message as last_message, m3.created_time
from conversation as c
join messages as m1 on c.conversation_id = m1.conversation_id
left outer join messages as m2 on (c.conversation_id = m2.conversation_id
and (m1.created_time < m2.created_time OR m1.created_time = m2.created_time AND m1.message_id < m2.message_id))
join messages as m3 on c.conversation_id = m3.conversation_id
left outer join messages as m4 on (c.conversation_id = m4.conversation_id
and (m3.created_time > m4.created_time OR m3.created_time = m4.created_time AND m3.message_id > m4.message_id))
where user_id1 = 103 and m2.message_id is null and m4.message_id is null
union all
select c.conversation_id, c.user_id1 as conversation_with, m1.message as last_message, m3.created_time
from conversation as c
join messages as m1 on c.conversation_id = m1.conversation_id
left outer join messages as m2 on (c.conversation_id = m2.conversation_id
and (m1.created_time < m2.created_time OR m1.created_time = m2.created_time AND m1.message_id < m2.message_id))
join messages as m3 on c.conversation_id = m3.conversation_id
left outer join messages as m4 on (c.conversation_id = m4.conversation_id
and (m3.created_time > m4.created_time OR m3.created_time = m4.created_time AND m3.message_id > m4.message_id))
where user_id2 = 103 and m2.message_id is null and m4.message_id is null
) as conversations
order by created_time desc
Related
I have been stuck on this for sometime now.
I have the following SQL Tables:
department table
Id Name
1 DeptA
2 DeptB
3 DeptC
users table
Id Name Dept
101 Alice 2
102 Bob 3
alpha table
Id Uid Title
501 101 HELLO
502 102 HEY
503 101 SQL
beta table
Id Uid Title
601 101 HELLO1
602 101 HEY1
603 102 SQL1
Explanation:
There's basically a users table which has all the users.
Each user has a department (Dept field)
Each user has some records, linked to it with Uid, in alpha and beta tables.
The result I want:
DeptA DeptB DeptC
0 4 2
I want the count of records in alpha and beta combined, grouped by Dept of the users whose records are there in these tables.
Can someone help me with the SQL query?
As per your table structure I've used dept id for retrieving result otherwise I used dept name. You can also use COALESCE function if you get NULL
-- MySQL
SELECT SUM(CASE WHEN d.id = 1 THEN COALESCE(total, 0) END) dept_A
, SUM(CASE WHEN d.id = 2 THEN COALESCE(total, 0) END) dept_B
, SUM(CASE WHEN d.id = 3 THEN COALESCE(total, 0) END) dept_C
FROM department d
LEFT JOIN (SELECT u.dept
, COUNT(1) total
FROM users u
INNER JOIN (SELECT uid
FROM alpha
UNION ALL
SELECT uid
FROM beta) t
ON u.id = t.uid
GROUP BY u.dept ) p
ON d.id = p.dept;
Please check url http://sqlfiddle.com/#!9/020b2/1
I am trying to write an SQL statement producing the below output.
I have the two following tables:
UserMovie
userID | movieID
-----------------
135 | k0jps
135 | p1zka
125 | v0t67
115 | opp2s
111 | xnwri
115 | kspdl
Follows
followerid | followingid
------------------------
122 | 135
192 | 111
125 | 240
120 | 125
45 | 111
I want to fetch the number of followers of each user who's userid is in the UserMovie Table, giving the following result:
Result
userid | followerCount
----------------------
135 | 1
125 | 1
115 | 0
111 | 2
The following statement gives me partially what i want:
SELECT followingid, count(*) as followerCount
FROM Follows
WHERE followingid in (SELECT DISTINCT userID FROM UserMovie)
GROUP BY followingid
The issue with the above query is that users with 0 followers do not appear in the results giving the following output:
userid | followerCount
----------------------
135 | 1
125 | 1
111 | 2
Any idea on how to do it?
Try this to include users with no follows:
SELECT UserId, Count(followerid) AS followerCount
FROM (SELECT DISTINCT userId FROM UserMovie ) m
LEFT JOIN Follows f
ON f.followingid = m.userID
GROUP BY UserId
Now it generates :
UserId followerCount
111 2
115 0
125 1
135 1
The following worked for me.
However I am getting NULLs instead of 0 for users with no followers
SELECT DISTINCT u.userid, t.followerCount
FROM UserMovie u
LEFT JOIN (
SELECT followingid, count(*) AS followerCount
FROM Follows
WHERE followingid in (SELECT DISTINCT userID FROM UserMovie)
GROUP BY followingid ) as t
on t.followingid = u.userid
How about a solution using CASE?
SELECT userId,
CASE
WHEN IFNULL(followerid, 0) = 0 THEN 0
ELSE count(*)
END
FROM UserMovie
LEFT JOIN Follows on followingid=userID
GROUP BY userId;
Seems to work fine in SQLite3, just replace IFNULL with ISNULL (if SQLServer) or any other equivalent. It's pretty similar to what you've done.
Here's one approach: get a distinct list of userID from UserMovie in an inline view (use either a GROUP BY or a DISTINCT keyword), and perform an "outer join" operation of that to the Followers table to find followers. Collapse the rows from that with a GROUP BY, and use an aggregate function to get a count of unique/distinct non-null values of userId from the Followers table.
For example:
SELECT u.userID
, COUNT(DISTINCT f.userID) AS cnt_followers
FROM ( SELECT m.userID
FROM UserMovie m
GROUP BY m.userID
) u
LEFT
JOIN Follows f
ON f.followingid = u.userID
GROUP BY u.userID
EDIT
There's an invalid column reference in the SELECT list, f.userID is not valid. That should be f.followerID.
When we fix that, the query returns:
userID cnt_followers
111 2
115 0
125 1
135 1
SQL Fiddle HERE http://sqlfiddle.com/#!9/de3e7/2
As long as we are counting "distinct" followerid (question doesn't give any guarantee that (followerID,followingID) is UNIQUE in Followers table), we could eliminate the inline view
SELECT u.userID
, COUNT(DISTINCT f.userID) AS cnt_followers
FROM UserMovie u
LEFT
JOIN Follows f
ON f.followingid = u.userID
GROUP BY u.userID
I have user1 who exchanged messages with user2 and user4 (these parameters are known). I now want to select the latest sent or received message for each conversation (i.e. LIMIT 1 for each conversation).
SQLFiddle
Currently my query returns all messages for all conversations:
SELECT *
FROM message
WHERE (toUserID IN (2,4) AND userID = 1)
OR (userID IN (2,4) AND toUserID = 1)
ORDER BY message.time DESC
The returned rows should be messageID 3 and 6.
Assuming that higher id values indicate more recent messages, you can do this:
Find all messages that involve user 1
Group the results by the other user id
Get the maximum message id per group
SELECT *
FROM message
WHERE messageID IN (
SELECT MAX(messageID)
FROM message
WHERE userID = 1 -- optionally filter by the other user
OR toUserID = 1 -- optionally filter by the other user
GROUP BY CASE WHEN userID = 1 THEN toUserID ELSE userID END
)
ORDER BY messageID DESC
Updated SQLFiddle
You can do this easily by separating it into two queries with ORDER BY and LIMIT then joining them with UNION:
(SELECT *
FROM message
WHERE (toUserID IN (2,4) AND userID = 1)
ORDER BY message.time DESC
LIMIT 1)
UNION
(SELECT *
FROM message
WHERE (userID IN (2,4) AND toUserID = 1)
ORDER BY message.time DESC
LIMIT 1)
The parenthesis are important here, and this returns messages 2 and 6, which seems correct, not 3 and 6.
It also seems like you could use UNION ALL for performance instead of UNION because there won't be duplicates between the two queries, but it's better if you decide that.
Here's your data:
MESSAGEID USERID TOUSERID MESSAGE TIME
1 1 2 nachricht 1 123
2 1 2 nachricht 2 124
3 2 1 nachricht 3 125
4 3 2 nachricht wrong 1263
5 2 4 nachricht wrong 1261
6 4 1 nachricht sandra 126
The below works as required:
SELECT m1.*
FROM Message m1
LEFT JOIN Message m2
ON LEAST(m1.toUserID, m1.userID) = LEAST(m2.toUserID, m2.userID)
AND GREATEST(m1.toUserID, m1.userID) = GREATEST(m2.toUserID, m2.userID)
AND m2.time > m1.Time
WHERE m2.MessageID IS NULL
AND ( (m1.toUserID IN (2,4) AND m1.userID = 1)
OR (m1.userID IN (2,4) AND m1.toUserID = 1)
);
To simplify how this works, imagine you just wanted the latest message sent by userid 1, rather than having to match the to/from tuples as this adds clutter to the query that doesn't help. To get this I would use:
SELECT m1.*
FROM Message AS m1
LEFT JOIN Message AS m2
ON m2.UserID = m1.UserID
AND m2.time > m1.time
WHERE m1.UserID = 1
AND m2.MessageID IS NULL;
So, we are joining similar messages, stipulating that the second message (m2) has a greater time than the first, where m2 is null it means there is not a similar message with a later time, therefore m2 is the latest message.
Exactly the principal has been applied in the solution, but we have a more complicated join to link conversations.
I have used LEAST and GREATEST in the join, the theory being that since you have 2 members in your tuple (UserID, ToUserID), then in any combination the greatest and the least will be the same, e.g.:
From/To | Greatest | Least |
--------+-----------+-------+
1, 2 | 2 | 1 |
2, 1 | 2 | 1 |
1, 4 | 4 | 1 |
4, 1 | 4 | 1 |
4, 2 | 4 | 2 |
2, 4 | 4 | 2 |
As you can see, in similar From/To the greatest and the least will be the same, so you can use this to join the table to itself.
There are two parts of your query in the following order:
You want the latest outgoing or incoming message for a conversation between two users
You want these latest messages for two different pairs of users, i.e. conversations.
So, lets get the latest message for a conversation between UserID a and UserID b:
SELECT *
FROM message
WHERE (toUserID, userID) IN ((a, b), (b, a))
ORDER BY message.time DESC
LIMIT 1
Then you want these to be combined for the two conversations between UserIDs 1 and 2 and UserIDs 1 and 4. This is where the union comes into play (we do not need to check for duplicates, thus we use UNION ALL, thanks to Marcus Adams, who brought that up first).
So a complete and straightforward solution would be:
(SELECT *
FROM message
WHERE (toUserID, userID) IN ((2, 1), (1, 2))
ORDER BY message.time DESC
LIMIT 1)
UNION ALL
(SELECT *
FROM message
WHERE (toUserID, userID) IN ((4, 1), (1, 4))
ORDER BY message.time DESC
LIMIT 1)
And as expected, you get message 3 and 6 in your SQLFiddle.
There are a number of left join questions already, but still I can't quite put my finger on this issue. The WHERE condition doesn't look sensible to move.
The problem is that there should be 4 rows returned but only 1 is.
In checking the left join conditions, there is 1 row returned for each left join, which is correct for the number of records in the table, however the query below returns 1 record instead of 4, but I can't see how to return 4, yet.
Query follows: (Gives 1 result not 4; 4 being expected)
SELECT
list.uid,
list.business_uid,
list.creator_name,
business.company_name,
list_alias.uid AS list_alias_uid,
list_alias.alias AS list_alias,
list_member.uid AS list_member_uid,
mailbox.full_name AS list_member_name,
mailbox.email_address AS list_member_email_address
FROM
mailbox,
business,
list
LEFT JOIN
list_alias ON list_alias.list_uid=list.uid
LEFT JOIN
list_member ON list_member.list_uid=list.uid
WHERE
list.business_uid='1'
AND list.business_uid=business.uid
AND mailbox.uid=list_member.mailbox_uid
ORDER BY
list.full_name ASC
Data:
Business UID 1 has 4 lists
SELECT * FROM list WHERE business_uid=1 -- gives 4 results
SELECT * FROM list_alias WHERE list_uid IN (SELECT uid FROM list WHERE business_uid=1) -- gives 1 result
SELECT * FROM list_member WHERE list_uid IN (SELECT uid FROM list WHERE business_uid=1) -- gives 1 result
Any pointers on what I could check would be welcome.
Table Sample Data:
list:
uid | business_uid | creator_name | full_name
--------------------------------------------------
1 1 List Maker Subscribe to W
2 1 List Maker Subscribe to X
3 1 List Maker Subscribe to Y
4 1 List Maker Subscribe to Z
business:
uid | company_name
-------------------
1 List Company
list_alias:
uid | list_uid | alias
----------------------------------------
1 1 subscriber#list-url.com
list_member:
uid | list_uid | mailbox_uid
------------------------------------
1 1 1
mailbox:
uid | full_name | email_address
-------------------------------
1 I am He me#me.com
Try this using a single join methodology, like so.
SELECT list.uid,
list.business_uid,
list.creator_name,
b.company_name,
la.uid AS list_alias_uid,
la.alias AS list_alias,
lm.uid AS list_member_uid,
m.full_name AS list_member_name,
m.email_address AS list_member_email_address
FROM list LEFT JOIN list_member lm ON lm.list_uid=list.uid
LEFT JOIN mailbox m ON m.uid=lm.mailbox_uid
LEFT JOIN business b ON list.business_uid=b.uid
LEFT JOIN list_alias la ON la.list_uid=list.uid
WHERE list.business_uid=1
ORDER BY list.full_name ASC
Question: What are the values of 'uid' from the 'list' table? Because 'uid' is not the same as 'business_uid'. What I mean is ...
If the 'list' table has this ...
'uid' 'business_uid'
1 1
2 1
3 1
4 1
Then that is the problem. You are returning the same 'busines_uid' but a different 'uid' which means it will only match the first record.
A conversation contains messages and one message can be send to 2 or more users.
The goal is to retrieve the last messages by user for each conversation, so where the messages in each conversation are the newest.
Also the message_user.time is when the user has read the message so when it's equals to 0 it's means the message has not been read yet so it'll be even better if the first order will be the message_user where time is equal to 0 and the the message.time that are the biggest numbers.
I got those tables
Message User
user message time
-----|------------|-------
7 | 1 | 0
8 | 1 | 0
7 | 2 | 300
8 | 2 | 300
7 | 3 | 400
Message
id text conversation time
---|-------------|--------------|----------
1 | blah blah1 | 1 | 200
2 | blah blah2 | 1 | 300
3 | blah blah3 | 2 | 400
4 | blah blah4 | 2 | 500
The goal is to group the messages by conversation and then get the last message_user linked to that conversation that have the bigger timestamp first.
What i tried is this but i don't get the last messages (wrong order)
SELECT m.user, mu.message, mu.time, mu.id, m.text, m.time as message_time, m.conversation
FROM message_user as mu,message as m
WHERE mu.message=m.id AND mu.user=8
GROUP BY m.conversation
ORDER BY m.time DESC';
Then the next step will be:
I don't know if it's possible but it if the order by can ouput the message_user where the time is equal to 0 first and then second order is message.time (i don't even know if it's possible in one request that will be perfect!)
For the user 8 the output should be this :
text conversation
-----------|------------
blah blah1 | 1 //because message_user.time = 0 (means message is unread)
blah blah3 | 2 //because message.time is the highest in the conversation
Thank you !
First, I'd suggest restructuring your tables to make this easier, but here's what I think you are asking for... Each conversation, showing only the last conversion message created, with the last time and user who read it (but prefer unread over this).
SELECT m.user as User_Sent, m.conversation, m.text, m.time as Time_Sent
, mu.id, mu.user as User_Read, mu.time as Time_Read
FROM message as m
JOIN (
SELECT mx.conversation, MAX(mx.time) as MaxTime
FROM message as mx GROUP BY mx.conversation
) as mx
On m.conversation = mx.conversation
And m.time = mx.MaxTime
JOIN (
SELECT mu.message, MAX(mu.time) as MaxTime, MIN(mu.time) as MinTime
From message_user as mu
GROUP BY mu.message
) as mux
On m.id = mux.message
JOIN message_user as mu
ON m.id = mu.message
AND CASE WHEN mux.MinTime = 0 THEN mux.MinTime ELSE mux.MaxTime END = mu.time