Mysql find rows equal to some values but limited by count - mysql

I have a conversation app where users are assigned to conversations.
One conversation can have 1, 2 or more participants, I use a table to link users to conversations :
When user 5 wants to initiate a new conversation with user 6, I don't want to create a new conversation because user 5 and 6 already have an ongoing conversation_id.
They actually also have a different group conversation with user 10
I have 2 parameters : it's user 5 that wants to speak to user 6
How can I query this table to find if a conversation exists only between these 2 users?
I tried this :
SELECT *,count(id) FROM `conversation_members`
WHERE user_id IN (5,6)
GROUP BY `conversation_id`
But
This also returns conversation id 2, while I would like to have
only id 1, so I would like to limit the select query to elements
having a count of 2, If I do the group by before I lose the ability
to match the users.
This also returns id 7 because it matches 1 of the 2 user_ids
it should match both
In the end I just want to retrieve ID 1 as this is the conversation including user 5 and 6 and nobody else. Is this possible only with a Mysql Query?

You can do:
SELECT conversation_id
FROM conversation_members
GROUP BY conversation_id
HAVING SUM(user_id IN (5, 6)) = 2 AND
COUNT(*) = 2;
This will return all conversations (if any) that have exactly those members.

Use the sum of a case expression along with the count in the having clause:
SELECT conversation_id
FROM conversation_members
GROUP BY conversation_id
HAVING SUM(case when user_id IN (5, 6) then 1 end) = 2
AND COUNT(*) = 2;
The ability to use this HAVING SUM(user_id IN (5, 6)) = 2 is not common and is a "MySQL curiosity" I believe. So I suggest you use a case expression instead which I think is also easier to read/understand.

Related

How to count number of user chat with another user

I try few mysql statement but didn't come to my expectations.
How to get the total of to_user chat and order by the lowest total?
Let say in this case,
id 7 chat with 2 user
id 6 chat with only 1 user.
so the minimum will be id 6.
Can someone help me with sql statement?
This is what my expected result
count
to_user
1
7
2
6
I think your problem will be solved with the following code:
SELECT COUNT(DISTINCT(from_user)) AS total,to_user
FROM chats
GROUP BY to_user
ORDER BY total ASC
Make sure you index the junction in both directions, otherwise it will BYITA later!
ALTER TABLE messages
ADD INDEX idx_from_to (from_user, to_user),
ADD INDEX idx_to_from (to_user, from_user);
You may want to take into account the fact that the full list of chats for a given user is (from any user, to given user) UNION (to any user, from given user).
Consider user 1 in your sample data, who has sent messages to 5, 6, & 7 but not received any. And user 5 has sent to user 7 and received from user 1.
SELECT COUNT(DISTINCT(from_user)) AS count, to_user
FROM messages
GROUP BY to_user
ORDER BY count ASC
returns the following (which matches the expected result detailed in your question, errors aside)
count
to_user
1
5
1
6
2
7
whereas
SELECT COUNT(*) AS count, from_user AS user
FROM (
SELECT from_user, to_user FROM messages
UNION
SELECT to_user, from_user FROM messages
) t
GROUP BY from_user
ORDER BY count ASC;
returns
count
user
1
6
2
5
2
7
3
1

GROUP BY chat_id - Return only if rows of all criteria are matched

I have a table of chat participants that looks like this:
id chat_id participant_id
1 1 5
2 1 7
3 1 9
If the user creates a new chat, we first want to make sure a chat with the requested participants does not already exist, if it does we will just use that chat.
So here let's say I want to create a chat with the participants 5, 7, 9, and 11.
In this case, nothing would be returned and a new chat with a new chat_id would have to be created, since there is no chat that contains all of these members.
However, if we want to create a chat with just participants 5, 7, and 9, the chat_id of 1 should be returned.
Is there an efficient way to structure a query to achieve this without using loops?
I tried something like this but never got desired results:
SELECT chat_id FROM chat_participants WHERE participant IN(5,7,9) GROUP BY chat_id
Any suggestions?
you just need to add condition with count of your requested participants total like this
SELECT chat_id FROM chat_participants WHERE participant IN(5,7,9) GROUP
BY chat_id having count(chat_id) = 3
if you pass like IN(5,7,9,11) than your condition will be having count(chat_id) = 4
select chat_id
from
(
select chat_id, group_concat(participant order by participant, ',') as gc
from chat_participants
where chat id in (select chat_id from chat_participants where participant IN(5,7,9))
group by chat_id
) x1
where x1.gc = '5,7,9'

MySQL : Group By Clause Not Using Index when used with Case

Im using MySQL
I cant change the DB structure, so thats not an option sadly
THE ISSUE:
When i use GROUP BY with CASE (as need in my situation), MYSQL uses
file_sort and the delay is humongous (approx 2-3minutes):
http://sqlfiddle.com/#!9/f97d8/11/0
But when i dont use CASE just GROUP BY group_id , MYSQL easily uses
index and result is fast:
http://sqlfiddle.com/#!9/f97d8/12/0
Scenerio: DETAILED
Table msgs, containing records of sent messages, with fields:
id,
user_id, (the guy who sent the message)
type, (0=> means it's group msg. All the msgs sent under this are marked by group_id. So lets say group_id = 5 sent 5 msgs, the table will have 5 records with group_id =5 and type=0. For type>0, the group_id will be NULL, coz all other types have no group_id as they are individual msgs sent to single recipient)
group_id (if type=0, will contain group_id, else NULL)
Table contains approx 10 million records for user id 50001 and with different types (i.e group as well as individual msgs)
Now the QUERY:
SELECT
msgs.*
FROM
msgs
INNER JOIN accounts
ON (
msgs.user_id = accounts.id
)
WHERE 1
AND msgs.user_id IN (50111)
AND msgs.type IN (0, 1, 5, 7)
GROUP BY CASE `msgs`.`type` WHEN 0 THEN `msgs`.`group_id` ELSE `msgs`.`id` END
ORDER BY `msgs`.`group_id` DESC
LIMIT 100
I HAVE to get summary in a single QUERY,
so msgs sent to group lets say 5 (have 5 records in this table) will be shown as 1 record for summary (i may show COUNT later, but thats not an issue).
The individual msgs have NULL as group_id, so i cant just put 'GROUP BY group_id ' coz that will Group all individual msgs to single record which is not acceptable.
Sample output can be something like:
id owner_id, type group_id COUNT
1 50001 0 2 5
1 50001 1 NULL 1
1 50001 4 NULL 1
1 50001 0 7 5
1 50001 5 NULL 1
1 50001 5 NULL 1
1 50001 5 NULL 1
1 50001 0 10 5
Now the problem is that the GROUP condition after using CASE (which i currently think that i have to because i only need to group by group_id if type=0) is causing alot of delay coz it's not using indexes which it does if i dont use CASE (like just group by group_id ). Please view SQLFiddles above to see the explain results
Can anyone plz give an advice how to get it optimized
UPDATE
I tried a workaround , that does somehow works out (drops INITIAL queries to 1sec). Using union, what it does is, to minimize the resultset by union that forces SQL to write on disk for filesort (due to huge resultset), limit the resultset of group msgs, and individual msgs (view query below)
-- first part of union retrieves group msgs (that have type 0 and needs to be grouped by group_id). Applies the limit to captivate the out of control result set
-- The second query retrieves individual msgs, (those with type !=0, grouped by msgs.id - not necessary but just to be save from duplicate entries due to joins). Applies the limit to captivate the out of control result set
-- JOins the two to retrieve the desired resultset
Here's the query:
SELECT
*
FROM
(
(
SELECT
msgs.id as reference_id, user_id, type, group_id
FROM
msgs
INNER JOIN accounts
ON (msgs.user_id = accounts.id)
WHERE 1
AND accounts.id IN (50111 ) AND type = 0
GROUP BY msgs.group_id
ORDER BY msgs.id DESC
LIMIT 40
)
UNION
ALL
(
SELECT
msgs.id as reference_id, user_id, type, group_id
FROM
msgs
INNER JOIN accounts
ON (
msgs.user_id = accounts.id
)
WHERE 1
AND msgs.type != 0
AND accounts.id IN (50111)
GROUP BY msgs.id
ORDER BY msgs.id
LIMIT 40
)
) AS temp
ORDER BY reference_id
LIMIT 20,20
But has alot of caveats,
-I need to handle the limit in inner queries as well. Lets say 20recs per page, and im on page 4. For inner queries , i need to apply limit 0,80, since im not sure which of the two parts had how many records in the previous 3 pages. So, as the records per page and number of pages grow, my query grows heavier. Lets say 1k rec per page, and im on page 100 , or 1K, the load gets heavier and time exponentially increases
I need to handle ordering in inner queries and then apply on the resultset prepared by union , conditions need to be applied on both inner queries seperately(but not much of an issue)
-Cant use calc_found_rows, so will need to get count using queries seperately
The main issue is the first one. The higher i go with the pagination , the heavier it gets
Would this run faster?
SELECT id, user_id, type, group_id
FROM
( SELECT id, user_id, type, group_id, IFNULL(group_id, id) AS foo
FROM msgs
WHERE user_id IN (50111)
AND type IN (0, 1, 5, 7)
)
GROUP BY foo
ORDER BY `group_id` DESC
LIMIT 100
It needs INDEX(user_id, type).
Does this give the 'correct' answer?
SELECT DISTINCT *
FROM msgs
WHERE user_id IN (50111)
AND type IN (0, 1, 5, 7)
GROUP BY IFNULL(group_id, id)
ORDER BY `group_id` DESC
LIMIT 100
(It needs the same index)

Count 2 columns on different conditions

I have this dataset:
id uid follows_uid status
1 1 2 ACTIVE
2 1 3 ACTIVE
3 3 1 ACTIVE
4 4 1 ACTIVE
5 2 1 ACTIVE
on giving uid I want to calculate how many users are following, and how many are followed by (the given user).
Result set will be:
following followers
2 3
and here is the query which does the work:
SELECT COUNT(*) as following,
(SELECT COUNT(*) FROM user_followers where follows_uid = 1 ) as followers
FROM user_followers
WHERE uid = 1 and `status` = 'ACTIVE'
Now the question is, In't there any other way to get this done? Or is it the best way to achieve this?
If you have separate indexes on uid and follows_uid, then I believe using subqueries as you did is the fastest way to retrieve the separate counts because each query will take advantage of an index to retrieve the count.
Here's another way of achieving it.
select following.*, followers.* from
(select count(uid) from user_followers where uid = 1) following,
(select count(follows_uid) from user_followers where follows_uid = 1) followers;
And, to answer your question, your subquery approach is, in fact, the best way to achieve it. As pointed out by #FuzzyTree, you could use indexes to optimise your performance.
SELECT
IFNULL(SUM(IF(uid = 1, 1, 0)), 0) as following,
IFNULL(SUM(IF(follows_uid = 1, 1, 0)), 0) as followers
FROM user_followers
WHERE (uid = 1 OR follows_uid = 1)
AND `status` = 'ACTIVE';
Click here to see SQL Fiddle

Get latest entry from multiple entries for the same user from a table

I have 2 tables. Table A and Table B.
Table A contains the details of individual users.
Table B contains 3 columns, namely "is_completed", "user_id" and "work_id"......
Table B tracks the details of work done by users and whether the work is completed or not. If completed, then that user can be assigned another work.
Problem Statement :
I assigned a work to user 1 and his is_completed is 0 (work not finished)...now I assume that after some days, his work is finished, so I did is_completed as 1 but at the same time I assigned another work to the same user 1 and now is_completed is 0. So I have two rows of same user, one with is_completed as 1 and another is_completed as 0 in Table B.
How can I fetch the latest is_completed i.e. user 1 as working or say busy?
SELECT COUNT(DISTINCT t.work_id) AS working FROM
(
SELECT * FROM
FROM TableB
WHERE user_id = 1
ORDER BY work_id DESC LIMIT 2;
) AS t
Result:
+---------+
| working |
+---------+
| 1 | // not working
| 2 | // working
+---------+
This query will return 2 if user 1 is currently in the middle of a task, indicating that there is only one record for the most recent work_id. It will return 1 if the user has finished his previous task and has not yet received a new task, indicating two records (start and stop) for the most recent work_id.
I assume that the work_id which gets assigned is always increasing.
Like this:
select u.user_id, if(ifnull(w.is_completed, 0) = 1, 'Busy', 'Available') as Status
from users u
left join work w
on u.user_id = w.user_id
left join work w2
on w.user_id = w2.user_id and w.work_id < w2.work_id
where w2.work_id is null
Fiddle: http://sqlfiddle.com/#!9/03aa7/9
Query will return ALL users and their current availability as either 'Busy' or 'Available', depending on the status of their most recent work entry. Note this depends on the notion that work_id is an ascending, never repeating value, and that a work_id greater than another work_id, is guaranteed to be the more recent of the two.
If you want it to show the status for a specific user_id, just append AND user_id = ?? to the above query
select t.user_id,t.is_completed from( select * from
TableB
order by work_id desc )as t group by t.user_id
This will give latest work staus of a user