MySQL: Select all that only have two rows, with specific values? - mysql

I would like to grab all the users that ONLY have two roles, which are 1 and 4.
One user role is stored like this:
user_id role_id
54321 1
54321 4
54322 1
54323 1
How can i make a query, that grabs the user_id 54321, because it Only have two roles and these two are 1 and 4?
I can use WHERE role_id IN (1, 4) but this will also grab users that have other roles.

WHERE role_id IN (1, 4) GROUP BY user_ID HAVING COUNT(DISTINCT role_id) = 2
http://gregorulm.com/relational-division-in-sql-the-easy-way/

This is an example of a set-within-sets query. I like to solve these with group by and having because that is the most general approach:
select user_id
from user_roles ur
group by user_id
having sum(role_id = 1) > 0 and
sum(role_id = 4) > 0 and
sum(role_id not in (1, 4)) = 0;
The having clause has three conditions. The first counts the number of times that role is 1, and the user_id passes if there is at least one such role. The second does the same for 4. The last checks that there are no other values.
I like this structure because it is flexible. If the condition were 1 and 4 and others are allowed, you would just drop the third clause. If the condition were 1 or 4 and no others, then it would look like:
having (sum(role_id = 1) > 0 or
sum(role_id = 4) > 0
) and
sum(role_id not in (1, 4)) = 0;

SELECT u3.user_id
FROM t u1, t u2, t u3
WHERE u1.role_id = 1 AND u2.role_id = 4
AND u3.user_id = u1.user_id
AND u2.user_id = u1.user_id
GROUP BY u3.user_id HAVING COUNT(u3.role_id) = 2;

Related

Getting Follower and FollwedBy Users from Table in one list

I have a Table that tracks followers
FollowerUserId, FollowingUserId
1 2
2 1
3 1
4 1
1 5
I want to get all user that given Id follows and is followed by or Both.
for example for UserId 1,I want result to be: (FG: Following, FD: Followed, B: Both ways)
2,B
5,FG
3,FD
4,FD
i can easily get FG and FD by doing union
Select FollowerUserId, 'FD' From Table Where FollowingUserId =1
Union
Select FollowingUserId, 'FG' From Table Where FollowerUserId =1;
with above i get user 2 as
2,FG
2,FD
from above but I really need 2,B without UserId 2 duplicated.
How can this be done efficiently?
You can use aggregation on your basic query:
SELECT UserId,
(CASE WHEN COUNT(DISTINCT which) = 1 THEN MIN(which)
ELSE 'B'
END)
FROM (Select FollowerUserId as UserId, 'FD' as which From Table Where FollowingUserId = 1
Union ALL
Select FollowingUserId, 'FG' From Table Where FollowerUserId = 1
) f
GROUP BY UserId;

select rows in mysql having another column with same value

I have a table that manage conversations of a chat between users, the structure is the following.
id | user_id | conversation_id
let's say that on the conversation with ID 1 there are 3 people to chat and the conversation with ID 2, 2 people as well
Conversations_users table will look like this
id | user_id | conversation_id
1 1 1
2 2 1
3 4 1
4 3 2
5 4 2
Now having only the id of the users 3 and 4 and Not Conversation ID I would like select the conversation that belongs to that users so a verbal query should be:
Select from conversations_users, where in user_id = 3 and 4 and conversation_id is equals to conversation id of user 3 and 4
how can I build this "verbal query" in Mysql?
to get all the users in the conversations that user 3 and 4 are part of you could use this:
select distinct(user_id) from conversation_table where conversation_id in (select distinct(conversation_id) from conversation_table where user_id in (3,4));
it won't be very fast though
to get their actual conversations, I'm assuming you have a different table with the text in it:
you probably want something like this
select distinct(u.user_id), c.text from conversation_table u left join conversations c on c.id=u.conversation_id where u.conversation_id in (select distinct(conversation_id) from conversation_table where user_id in (3,4));
here is an sqlfiddle
Here is one method:
select uc.conversation_id
from UserConversions uc
where uc.user_id in (3, 4)
group by uc.conversation_id
having count(*) = 2;
If the table could have duplicates, you'll want: having count(distinct user_id) = 2.
EDIT:
If you want a specific list, just move the where condition to the having clause:
select cu.conversation_id
from conversations_users cu
group by cu.conversation_id
having sum(cu.user_id in (3, 4)) = 2 and
sum(cu.user_id not in (3, 4)) = 0;
I assume you have another table called "conversations" which holds the data you really want.
SELECT *
FROM conversations, conversations_users
WHERE conversations_users.user_id in (3,4)
AND conversations.id = conversations_users.conversation_id

MYSQL: Find all posts between certain users

I have two tables:
table message (holds the creator of a message and the message)
id - creatorId - msg
and a table message_viewers (tells who can read the message msgId)
msgId - userId
If I create a message as user 1 and send it to user 2 and user 3, the tables will look like this:
tbl_message:
1 - 1 - 'message'
tbl_message_viewers:
1 - 2
1 - 3
What I want to do is to fetch the messages that are between the users x1...xN (any number of users) AND ONLY the messages between them.
(Example if users are 1, 2, and 3, I want the messages where the creator is 1, 2 or 3, and the users are 2,3 for creator = 1, 1 and 3 for creator = 2 and 1, 2 for creator = 3)
I am not interested by messages between 1 and 2, or 2 and 3, or 1 and 3, but only by messages between the 3 people.
I tried different approaches, such as joining the two tables on message id, selecting the messages where creatorId IN (X,Y) and then taking only the rows where userId IN (X, Y) as well. Maybe something about grouping and counting the rows, but I could not figure out a way of doing this that was working.
EDIT: SQL Fiddle here
http://sqlfiddle.com/#!2/963c0/1
I think this might do what you want:
SELECT m.*
FROM message m
INNER JOIN message_viewers mv ON m.id = mv.msgId
WHERE m.creatorId IN (1, 2, 3)
AND mv.userId IN (1, 2, 3)
AND NOT EXISTS (
SELECT 1
FROM message_viewers mv2
WHERE mv2.msgId = mv.msgId
AND mv2.userId NOT IN (1, 2, 3)
)
AND mv.userId != m.creatorId;
The IN's will give the users that created/can see, and the mv.userId != m.creatorId are for excluding the creator from the message_viewers table (like you showed in your requirements).
Edit:
With the requirement of only sending messages between those 3 id's, i came up with the following:
SELECT m.id,m.creatorId,m.message
FROM message m
INNER JOIN message_viewers mv ON m.id = mv.msgId
WHERE m.creatorId IN (1, 2, 3)
AND mv.userId IN (1, 2, 3)
AND mv.userId != m.creatorId
AND NOT EXISTS (
SELECT 1
FROM message_viewers mv2
WHERE mv2.msgId = mv.msgId
AND mv2.userId NOT IN (1, 2, 3)
)
GROUP BY 1,2,3
HAVING COUNT(*) = 2;
sqlfiddle demo
Try this with join and with IN() clause
SELECT * FROM
tbl_message m
JOIN tbl_message_viewers mv (m.id = mv.msgId )
WHERE m.creatorId IN(1,2,3) AND mv.userId IN(1,2,3)
Sounds like you might want the BETWEEN operator:
SELECT * FROM tablename WHERE fieldname BETWEEN 1 AND 10;
-- returns fieldname 1-10
In this case however, BETWEEN is inclusive, so you'll need to specify != those conditions as well:
SELECT * FROM tablename WHERE fieldname BETWEEN 1 AND 10 AND fieldname NOT IN (1, 10)
-- returns fieldname 2-9
http://www.w3schools.com/sql/sql_between.asp
this worked on oracle
first join gets row count for people we are not interested in
second join gets row count for people we are interested in
the in clauses will need to be generated by some sort of dynamic sql
and number_of_people also needs to be generated somehow.
select msgId, count_1, count_2
from message tm
join ( select ty.msgId as ty_msgId,
count(ty.msgId) as count_1
from message_viewers ty
where ty.userId not in (:a,:b,:c)
group by ty.msgId)
on msgId = ty_msgId
join (select tz.msgId as tz_msgId,
count(tz.msgId) as count_2
from message_viewers tz
where tz.userId in (:a,:b,:c)
group by tz.msgId)
on msgId = tz_msgId
where createrId in(:a,:b,:c)
and count_1 = 0
and count_2 = :number_of_people -1;
my sql prefers this
select msgId, count_1, count_2
from message tm
left join ( select ty.msgId as ty_msgId,
count(ty.msgId) as count_1
from message_viewers ty
where ty.userId not in (:a,:b,:c)
group by ty.msgId) as X
on msgId = ty_msgId
left join (select tz.msgId as tz_msgId,
count(tz.msgId) as count_2
from message_viewers tz
where tz.userId in (:a,:b,:c)
group by tz.msgId) as Y
on msgId = tz_msgId
where createrId in(:a,:b,:c)
and (count_1 = 0 or count_1 is null)
and count_2 = :number_of_people -1;

MySQL - loop through rows

I have the following code
select count(*)
from (select Annotations.user_id
from Annotations, Users
where Users.gender = 'Female'
and Users.user_id = Annotations.user_id
and image_id = 1
group by Annotations.user_id
having sum(case when stem = 'taxi' then 1 else 0 end) > 0 and
sum(case when stem = 'zebra crossing' then 1 else 0 end) > 0
) Annotations
It produces a count of how many females who have given the stem 'taxi' and 'zebra crossing' for image 1.
Sample data
user id, image id, stem
1 1 image
1 1 taxi
1 1 zebra crossing
2 1 person
2 1 zebra crossing
2 1 taxi
3 1 person
3 1 zebra crossing
Expected result (or similar)
stem1, stem2, count
taxi , zebra crossing 2
person, zebra crossing 2
However, as there are over 2000 stems, I cannot specify them all.
How would I go around looping through the stem rows with the image_id = 1 and gender = female as opposed to specifying the stem string?
Thank you
As per my understanding, you need to fetch female users that have 2 or more stems
Update: It seems you need to display the user's that have a stem that is used by another user too, I have updated the query for the same
SELECT
distinct a.user_id,
group_concat(DISTINCT a.stem ORDER BY a.stem)
FROM
Annotations a
JOIN Users u ON ( a.user_id = u.user_id AND u.gender = 'Female' )
JOIN
(
SELECT
b.user_id,
b.stem
FROM
Annotations b
) AS b ON ( a.user_id <> b.user_id AND b.stem = a.stem )
WHERE
a.image_id = 1
GROUP BY
a.user_id
UPDATE: As I understand it, you want to select all combinations of 2 stems, and get a count of how many users have that combination of stems. Here is my solution:
SELECT stem1, stem2, count(*) as count FROM
(
SELECT a.user_id,a.image_id,a.stem as stem1,b.stem as stem2
FROM Annotations a JOIN Annotations b
ON a.user_id=b.user_id && b.image_id=a.image_id && a.stem!=b.stem
JOIN Users ON Users.user_id = a.user_id
WHERE Users.gender = "Female"
) as stems GROUP BY stem1, stem2 having count > 1 WHERE image_id=1;
The caveat here is that it will return 2 rows for each combinations of stems. (The second occurrence will have the stems in reverse order).
Here's my attempt to solve your problem:
SELECT COUNT(*) AS Count, a1.stem AS Stem1, a2.Stem AS Stem2
FROM Annotations AS a1
INNER JOIN Annotations AS a2 ON a1.user_id = a2.user_id AND a1.image_id = a2.image_id
AND a1.stem < a2.stem
WHERE a1.image_id = 1
GROUP BY a1.stem, a2.Stem
HAVING COUNT(*) > 1;
I did not include image_id logic.
Please see my SQL Fiddle here: http://sqlfiddle.com/#!2/4ee69/33
Based on the following data (copied from yours) I get the result posted underneath it.
CREATE TABLE Annotations
(`user_id` int, `image_id` int, `stem` varchar(14))
;
INSERT INTO Annotations
(`user_id`, `image_id`, `stem`)
VALUES
(1, 1, 'image'),
(1, 1, 'taxi'),
(1, 1, 'zebra crossing'),
(2, 1, 'person'),
(2, 1, 'zebra crossing'),
(2, 1, 'taxi'),
(3, 1, 'person'),
(3, 1, 'zebra crossing')
;
COUNT STEM1 STEM2
2 person zebra crossing
2 taxi zebra crossing

SQL: Where NOT IN query

So in the database, there's a table named roles_users. This holds all the roles that the users have. Here, there's two columns: user_id, role_id.
A normal user, with no extra roles has 1 row in this table. This row has role_id 1.
A admin user, has 2 rows in this table. One with role_id 1, and one row with role_id 2
Like this:
user_id role_id
88 1
88 2
99 1 // Only one row with that user_id, so he's a user
Now im trying to count how many users/admin/sellers/partners that exists.
Sellers have 3 rows, one with role_id 1, role_id 2 and role_id 3.
Partner has role_id 1, role_id 4
So i tried this:
SELECT user_id FROM roles_users WHERE role_id IN (1) // MEMBERS ONLY
SELECT user_id FROM roles_users WHERE role_id IN (1,2) // ADMIN
SELECT user_id FROM roles_users WHERE role_id IN (1,2,3) // SELLER
SELECT user_id FROM roles_users WHERE role_id IN (1,4) // PARTNERS
But these queries does not work properly. They give me a count that is way over than its supposed to be. And i believe this is because that it does not EXCLUDE any rows,
I mean at the query when it should look for role_id 1, for members, it includes partners,admin,sellers too because it only check for if it theres any with the row role_id 1 and thats it.
So how can i do this right? So when it looks after members, it should also make sure that the user_id does not have any more rows with other role_ids like 2,3,4
group_concat is what you want:
select
case roles
when '1' then 'members'
when '1,2' then 'admins'
when '1,2,3' then 'sellers'
when '1,4' then 'partners'
else 'uh??'
end role,
count(user_id) nr_users from (
select user_id, group_concat(role_id order by role_id separator ',') roles
from roles_users
group by user_id
)
group by role
order by role;
And by the way, you could store roles more efficiently using a bitmask. Advantage: only one column per user id. Disadvantage: harder to build queries...
This will give you all combinations of privileges in the table with their count
SELECT
COUNT(*) AS num
GROUP_CONCAT(role_id ORDER BY role_id) AS privilegeset
FROM roles_users
GROUP BY user_id
Concerning sellers and partners:
As I understand it, sellers are the only ones that can have role_id = 3 and partners are the only ones that can have role_id = 4, correct?
If yes, finding sellers and partners is quite easy:
SELECT user_id FROM roles_users WHERE role_id = 3 // SELLER
SELECT user_id FROM roles_users WHERE role_id = 4 // PARTNERS
Not the most elegant, but a start
SELECT user_id
,CASE WHEN (role1 > 0 AND 0 = role2 AND 0 = role3 AND 0 = role4) THEN 'MEMBER'
WHEN (role1 > 0 AND role2 > 0 AND 0 = role3 AND 0 = role4) THEN 'ADMIN'
WHEN (role1 > 0 AND role2 > 0 AND role3 > 0 AND 0 = role4) THEN 'SELLER'
WHEN (role1 > 0 AND 0 = role2 AND 0 = role3 AND role4 > 0) THEN 'PARTNER'
ELSE 'Something else'
END AS type
FROM (
SELECT user_id
,SUM( CASE role_id WHEN 1 THEN 1 ELSE 0 END) AS role1
,SUM( CASE role_id WHEN 2 THEN 1 ELSE 0 END) AS role2
,SUM( CASE role_id WHEN 3 THEN 1 ELSE 0 END) AS role3
,SUM( CASE role_id WHEN 4 THEN 1 ELSE 0 END) AS role4
FROM roles_users
GROUP BY user_id
) x
EDIT: I guess this is answering the wrong question, it is showing the type of each user.
Here's a solution using INTERSECT and MINUS. Sadly these are not supported by MySQL yet but maybe someone will find this useful nonetheless.
-- Find users with Role 1
SELECT user_id FROM
(SELECT user_id, role_id FROM roles_users WHERE role_id = 1)
MINUS SELECT user_id FROM roles_users WHERE role_id NOT IN (1)
-- Find users with Roles 1 and 2
SELECT user_id FROM
(SELECT user_id, role_id FROM roles_users WHERE role_id = 1
INTERSECT SELECT user_id, role_id FROM roles_users WHERE role_id = 2)
MINUS SELECT user_id FROM roles_users WHERE role_id NOT IN (1,2)
-- Find users with Roles 1, 2, 3
SELECT user_id FROM
(SELECT user_id, role_id FROM roles_users WHERE role_id = 1
INTERSECT SELECT user_id, role_id FROM roles_users WHERE role_id = 2
INTERSECT SELECT user_id, role_id FROM roles_users WHERE role_id = 3)
MINUS SELECT user_id FROM roles_users WHERE role_id NOT IN (1,2,3)
-- Find users with Roles 1, 4
SELECT user_id FROM
(SELECT user_id, role_id FROM roles_users WHERE role_id = 1
INTERSECT SELECT user_id, role_id FROM roles_users WHERE role_id = 4)
MINUS SELECT user_id FROM roles_users WHERE role_id NOT IN (1,4)
Here's a join-based solution that hopefully works on any SQL.
SELECT user_id FROM roles_users RU0
LEFT JOIN roles_users RU1 ON RU0.user_id = RU1.user_id AND RU1.role_id = 1
LEFT JOIN roles_users RU2 ON RU0.user_id = RU2.user_id AND RU2.role_id = 2
LEFT JOIN roles_users RU3 ON RU0.user_id = RU3.user_id AND RU3.role_id = 3
LEFT JOIN roles_users RU4 ON RU0.user_id = RU4.user_id AND RU4.role_id = 4
WHERE RU1.user_id IS NOT NULL -- should have role 1
AND RU2.user_id IS NULL -- should NOT have role 2
AND RU3.user_id IS NULL -- should NOT have role 3
AND RU4.user_id IS NOT NULL -- should have role 4
Just vary the "IS NULL" and "IS NOT NULL" in the where clause to change which roles you want the user to have or not have.