SQL: Where NOT IN query - mysql

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.

Related

COUNT with IF FROM DISTINCT values MySQL

I am trying to calculate the how many users are female and how many male, after doing DISTINCT on the UserID
SELECT
COUNT(IF("Gender" = 'female', 1, NULL)) as Ufemale,
COUNT(IF("Gender" = 'male', 1, NULL)) as Umale
FROM (SELECT DISTINCT UserID FROM user_stats where Year='2019' and Account='P') as UID
If I execute the
SELECT DISTINCT UserID FROM user_stats where Year='2019' and Account='P'
It returns the unique UserID. However, if I combine it with count gender part, it returns Zero.
Here is how the values looks like
UserID | Gender
-----------------------------
2018359084885123 | male
1925823664195671 | female
2033134076795519 |
2122445674469149 | female
2315129265210413 | female
2018359084885123 | male
2122445674469149 | female
And the aim is to show
Ufemale | Umale
-------------------
3 | 1
Try this:
Tables
drop table if exists test;
create table test (userid bigint, gender char(6));
insert into test values
(2018359084885123,'male')
,(1925823664195671,'female')
,(2033134076795519, null)
,(2122445674469149,'female')
,(2315129265210413,'female')
,(2018359084885123,'male')
,(2122445674469149,'female');
Query
select
sum(case when gender = 'female' then 1 else 0 end) as ufemale,
sum(case when gender = 'male' then 1 else 0 end) as umale
from
(select distinct userid, gender from test) x
Result
ufemale umale
3 1
Example: https://rextester.com/JLQ19855
There is no Gender column in the results of the subquery:
SELECT DISTINCT UserID FROM user_stats where Year='2019' and Account='P'
as you may think.
What you are doing is comparing 2 strings: "Gender" and 'female' which are obviously not equal so this:
IF("Gender" = 'female', 1, NULL)
returns NULL and COUNT(NULL) returns 0.
Where is the Gender column?
Is it in user_stats table or in another table like Users?
If it is in user_stats then you need to write the query like this:
select
sum(t.gender = 'female') ufemale,
sum(t.gender = 'male') umale
from (
select distinct userid, gender
from user_stats
where Year='2019' and Account='P'
) t
If it is in the users table then you will need a join first:
select
sum(u.gender = 'female') ufemale,
sum(u.gender = 'male') umale
from (
select distinct userid, gender
from user_stats
where Year='2019' and Account='P'
) t inner join users u
on u.userid = t.userid

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

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;

How to make my mysql queries for showing friends feeds?

Here is my table structure.(fun_friends)
id user_id,friend_id,status,createdat,updatedat
1 1 2 1 123456 125461
2 1 3 1 454545 448788
3 2 4 1 565659 898889
4 1 5 1 877878 878788
Here is the table structure of user_uploads
id user_id parent_id category_id title slug tags description video_type source video_link video_thumb
1 2 1 2 fun fun ['4','5'] coolvid 1 ytu link thumb
I need to show the latest upload of my friends
Can you tell me how can i join this tables together? i tried with
SELECT * FROM fun_friends WHERE (user_id= '".$_SESSION['user_row_id']."' AND `status` =1) OR (friend_id= '".$_SESSION['user_row_id']."' AND `status` =1)
and it is showing all friends of logged-in user
You can just join both table using user_id field. sample query bellow will return one record with latest user_uploads.id.
select *
from fun_friends a
inner join user_uploads b on a.user_id = b.user id
order by b.id desc limit 0,1
how about using UNION to get the friends user and wrapping it inside a subquery which later join on the other tabel,
SELECT usr.*
FROM user_uploads usr
INNER JOIN
(
SELECT user_ID AS ID
FROM Fun_Friends
WHERE friend_ID = 'ID_HERE'
UNION
SELECT friend_ID As ID
FROM Fun_Friends
WHERE ser_ID = 'ID_HERE'
) idList ON usr.user_ID = idList.ID

Simplify MySQL query

I have a MySQL query that I used to use to return mutual friends between two users, but now that I am recoding my website, I am trying to simplify this code, or at least make it better.
So here's my code below to check mutual friends:
SELECT a.friendID
FROM
(SELECT CASE WHEN userID = $id
THEN userID2
ELSE userID
END AS friendID
FROM friends
WHERE (userID = $id OR userID2 = $id)
AND state='1'
) a
JOIN
(SELECT CASE WHEN userID = $session
THEN userID2
ELSE userID
END AS friendID
FROM friends
WHERE (userID = $session OR userID2 = $session)
AND state='1'
) b
ON b.friendID = a.friendID
My table is set up like this:
userID -- userID2 -- state
1 ------- 2 ------- 1
2 ------- 3 ------- 1
1 ------- 3 ------- 0
(sorry, I don't know how to do the pretty database structure design, so if someone could edit that for me...)
but for the above, when user 1 is on user 3's profile, since user 1 and user 2 are friends, and user 2 and user 3 are friends, but user 1 and user 3 are not, it should return user 2 as a mutual friend. (state 1 means friendship accepted, state 0 means friendship pending, so only if state is 1 should it be counted as a friend)
Also note that userID and userID2 can be in any order, it depends on who requests who as a friend, so like the above query does, I need to also have the "friendID" returned, as the above query does right.
SELECT t.f FROM friends JOIN (
SELECT IF(userID = $id, userID2, userID) f
FROM friends
WHERE state AND $id IN (userID, userID2)
) t ON t.f IN (userID, userID2)
WHERE state AND $session IN (userID, userID2)
See it on sqlfiddle.
Not necessarily the fastest but simple to read:
Create View ActiveFriends As
Select
UserId, UserID2
From
friends
Where
State = '1'
Union All
Select
UserId2, UserID
From
friends
Where
State = '1'
Select
f1.userID2 as MutualFriendId
From
ActiveFriends f1
Inner Join
ActiveFriends f2
On f1.UserID2 = f2.UserID
Where
f1.UserID = $session And
f2.UserID2 = $id
http://sqlfiddle.com/#!2/5748f/1/0
You could do :
SELECT userID2
FROM friends_table
WHERE userID IN ( $id, $session )
AND state = 1
GROUP BY userID2
HAVING COUNT(userID2) >= 2
friends_table :
user_id | friend_id | state
-----------------------------
1 | 7 | 1
2 | 3 | 0
7 | 1 | 1
User 1 and 7 are friends, user 2 wants to be friends with user 3, but user 3 hasn't responded yet.
at the first glance I would say that using intersect is a good solution
so this query should do the trick for you
SELECT userID2
FROM friends
WHERE userID = $fromID
INTERSECT
SELECT userID2
FROM friends
WHERE userID = $theOtherID
EDIT:
another less clear solution would be
SELECT userID2
FROM friends INNER JOIN friends f2
USING (userID2)
WHERE friends.UserID = $fromID AND f2.userID = $theOtherID

Select the opposite row of a WHERE clause in a joined jquery result

I have a database with users and games. 1 game can have multiple users so I made a linking table called users_games. The crux is that a game can always only have 2 players since it is a board game. I know which player I am, i have my user_id and my email, but I would like to gain a result that gives me a list of all games I am in WITH the user_id and email of the other fellow. So a query that looks to all games I am in and give the other row, with the name of the player.
My tables:
games
id (int)
board (varchar) representation of the board
users
id (int)
email (varchar)
password (varchar MD5)
users_games
id (int)
user_id (int)
game_id (int)
For clarification this query
SELECT *
FROM `tic_users_games` AS ug
LEFT JOIN tic_users AS u
ON ug.user_id = u.id
RIGHT JOIN tic_games AS g
ON ug.game_id = g.id
And result
id user_id game_id id email password id board created updated
1 1 1 1 ME#gmail.com d56b699830e77ba53855679cb1d252da 1 0|0|0|0|0|0|0|0|0 2012-04-02 16:56:06 2012-04-02 16:56:06
2 2 1 2 FOE1#gmail.com d56b699830e77ba53855679cb1d252da 1 0|0|0|0|0|0|0|0|0 2012-04-02 16:56:06 2012-04-02 16:56:06
3 3 2 2 FOE2#gmail.com d56b699830e77ba53855679cb1d252da 2 0|0|0|0|0|0|0|0|0 2012-04-02 16:56:06 2012-04-02 16:56:06
4 1 2 1 ME#gmail.com d56b699830e77ba53855679cb1d252da 2 0|0|0|0|0|0|0|0|0 2012-04-02 16:56:06 2012-04-02 16:56:06
See: In the above case I just want 2 rows: game_id 1 and 2, with FOE1#... and FOE2#...
Thanks
This should do it for you. The syntax might not be exact for mysql but you should get there. Basically get all users that have games in the user_games table with the same game_ID as the games I am in:
SELECT
User_Games.Game_ID,
Users.ID,
Users.Email
FROM
Users
LEFT JOIN User_Games ON Users.ID = User_Games.User_Id
WHERE
Users.User_ID <> #yourUserID
AND EXISTS
(SELECT
NULL
FROM
User_Games AS MyUserGames
WHERE
User_Games.Game_ID = MyUserGames.Game_ID
AND MyUserGames.User_ID = #yourUserID)
Try this (where #userid represents the user you are searching games for, in this case, 1):
select *
from tic_users_games ug1
left join tic_users_games ug2 on ug1.game_id = ug2.game_id
left join tic_users u on ug2.user_id = u.id
right join tic_games g on ug2.game_id = g.id
where ug1.user_id = #userid
and ug2.user_id <> #userid
Demo: http://www.sqlfiddle.com/#!2/7b0f6/2