MySQL where in list but not in temp table - mysql

I am considering the following 2 tables
|------------| |-----------|
| user_roles | | roles |
|============| |===========|
| user_id | | role_id |
| role_id | | code_name |
|------------| |-----------|
I want to get all user_roles where user_id in a given list of user_ids. But I want to exclude all users who have a role with code_name = 'special_role'.
What would be the best way to do this?
For the purpose of an example, lets say I have the following:
user_roles: roles:
| user_id | role_id | | role_id | code_name |
|=========|=========| |=========|==============|
| 1 | 1 | | 1 | special_role |
| 1 | 2 | | 2 | another_role |
| 2 | 2 | |---------|--------------|
| 3 | 2 |
|---------|---------|
My thought was to use temp tables, like:
create temporary table if not exists all_user_ids as (
select ur.user_id as user_id, ur.role_id as role_id
from user_roles ur
where ur.user_id in (1,2,3)
);
create temporary table if not exists special_user_ids as (
select aui.user_id as user_id
from all_user_ids aui
join roles r on r.role_id = aui.role_id
where r.code_name = 'special_role'
);
create temporary table if not exists non_special_user_ids as (
select aui.user_id as user_id
from all_user_ids aui
where aui.user_id not in (special_user_ids.user_id)
);
Then for my final result, I could do:
select ur.user_id, ur.role_id
from user_roles ur
where ur.user_id in (non_special_user_ids.user_id)
But there's got to be a better way?!

You can use window functions - if you are running MySQL 8.0:
select *
from (
select ur.*, r.code_name, max(r.code_name = 'special_role') over(partition by user_id) has_special_role
from user_roles ur
inner join roles r on r.role_id = ur.role_id
) t
where has_special_role = 0
In earlier versions, one method is not exists:
select ur.*
from user_roles ur
where not exists (
select 1
from user_roles ur1
inner join roles r1 on r1.role_id = ur1.role_id
where ur1.user_id = ur.user_id and r1.code_name = 'special_role'
)

Just join. This should be pretty fast assuming you have keys set up.
SELECT * FROM user_roles JOIN role ON user_roles.role_id = role.role_id
WHERE user_roles.user_id IN(1,2,3 ...) AND role.code_name != "special_role"
Misunderstood the ask. If you want no users that have a special role at all:
SELECT * FROM user_roles WHERE user_id NOT IN(
SELECT user_id FROM user_roles JOIN role ON user_role.role_id = role.role_id
WHERE role.role_code = 'special_role')
AND user_id IN (1, 2, 3 ...)

Use IN and NOT IN for the 2 conditions:
select *
from user_roles
where user_id in (<list of usr_ids>)
and user_id not in (
select user_id from user_roles
where role_id = (select role_id from roles where code_name = 'special_role')
)
See the demo.

Related

MySQL: refind groups of user from group description

I have a problem to find the group membership for users. (yes it's not very clear like that)
For example:
I have 2 table:
- one contains a user list with their permission:
userId | permission
-------|-----------
1 | build
1 | play
1 | jump
2 | build
2 | jump
2 | run
3 | drink
3 | build
4 | run
-the second table contain the group and him permisson:
groupId | permission
--------|-----------
G1 | build
G1 | jump
G2 | play
G2 | jump
G3 | drink
G3 | run
G4 | drink
G5 | build
My goal is to find all the groups that the user can have:
userId | groupId
-------|-----------
1 | G1
1 | G2
1 | G5
2 | G1
2 | G5
3 | G4
3 | G5
I have created a request to find which users belong to the group but I can not do this for all my groups (I have more than 1000 group in my datasets):
SELECT DISTINCT userId
FROM (
SELECT userId, count(*) AS nbData
from table_a
WHERE permission in (
SELECT permission
from table_b
where groupId = 'g1'
)
group by userId
) as t
where nbData = (SELECT count(*) from table_b where groupId = 'g1');
An user belongs to a group if he has all the permission of the group. And the goal is to find every group of each user
If you don't have duplicates in either table, you can join the tables together on permissions and aggregate:
select up.userId, gp.groupId
from user_permissions up join
group_permissions gp
on up.permission = gp.permission
group by up.userId, gp.groupId
having count(*) = (select count(*)
from group_permissions gp2
where gp2.groupId = gp.groupId
);
The user is in the group if all the permissions for the group match.
Something like this might work:
SELECT DISTINCT
a.userId,
b.groupId
FROM
table_a a
JOIN table_b b
ON a.permission = b.permission
E.g.:
DROP TABLE IF EXISTS user_permissions;
CREATE TABLE user_permissions
(user_id INT NOT NULL
,permission VARCHAR(12) NOT NULL
,PRIMARY KEY(user_id,permission)
);
INSERT INTO user_permissions VALUES
(1,'build'),
(1,'play'),
(1,'jump'),
(2,'build'),
(2,'jump'),
(2,'run'),
(3,'drink'),
(3,'build'),
(4,'run');
DROP TABLE IF EXISTS group_permissions;
CREATE TABLE group_permissions
(group_id INT NOT NULL
,permission VARCHAR(12) NOT NULL
,PRIMARY KEY(group_id,permission)
);
INSERT INTO group_permissions VALUES
(101,'build'),
(101,'jump'),
(102,'play'),
(102,'jump'),
(103,'drink'),
(103,'run'),
(104,'drink'),
(105,'build');
SELECT DISTINCT u.user_id
, g.group_id
FROM user_permissions u
JOIN group_permissions g
ON g.permission = u.permission -- groups that users potentially belong to
LEFT
JOIN
( SELECT DISTINCT x.user_id
, y.group_id
FROM user_permissions x
LEFT
JOIN group_permissions y
ON y.permission = x.permission
LEFT
JOIN group_permissions z
ON z.group_id = y.group_id
LEFT
JOIN user_permissions a
ON a.user_id = x.user_id
AND a.permission = z.permission
WHERE a.user_id IS NULL
) n -- groups that users don't belong to (this could be much simpler ;-) )
ON n.user_id = u.user_id
AND n.group_id = g.group_id
WHERE n.user_id IS NULL;
+---------+----------+
| user_id | group_id |
+---------+----------+
| 1 | 101 |
| 1 | 105 |
| 1 | 102 |
| 2 | 101 |
| 2 | 105 |
| 3 | 105 |
| 3 | 104 |
+---------+----------+

Select a specific row with additional column that its value is from another column in one table

Suppose I have a table named users consist of columns: user_id, user_name, user_created_by.
+------------------+----------------------+-------------------+
| user_id + user_name + user_created_by +
+------------------+----------------------+-------------------+
| 1 | John | 1 |
| 2 | Ann | 1 |
| 3 | Paul | 2 |
| 4 | King | 2 |
| 5 | Dirk | 3 |
+------------------+----------------------+-------------------+
The value of user_created_by is the user_id who created that record. Now, I want to make a query that results one specific row with added column let's say user_created_by_name which is the user_name of the user_id from the user_created_by. Suppose we want to get "Paul"'s record with who (the name) create it (temporary new column). For ease of understanding this is my expected result:
+----------+--------------+-------------------+------------------------+
| user_id | user_name | user_created_by | user_created_by_name |
+----------+--------------+-------------------+------------------------+
| 3 | Paul | 2 | Ann |
+----------+--------------+-------------------+------------------------+
this is my query using codeigniter:
$query=$this->db->query("SELECT *,
(SELECT user_name FROM users WHERE user_id = user_created_by)
AS "user_created_by_name" FROM users WHERE user_id=3);
But my result are:
+----------+--------------+-------------------+------------------------+
| user_id | user_name | user_created_by | user_created_by_name |
+----------+--------------+-------------------+------------------------+
| 3 | Paul | 2 | NULL |
+----------+--------------+-------------------+------------------------+
You culd use a self join (join the same table two time) using alias for fere to the tables as different sets of data
SELECT a.user_id, a.user_name, a.user_created_by, b.user_name as user_created_by_name
from users a
inner join user b on a.user_created_by = b.user_id
where a.user_id = 3
use self join
select u1.user_id, u1.name as user_name,
u2.user_created_by
,u2.user_name as createdby from users u1
join users u2 on u1.user_id=u2.user_created_by
where u1.user_id=3
You can solve this problem using a JOIN.
$sql = "SELECT users.user_id, users.user_name, user_created_by_name.user_name,
FROM users JOIN users AS user_created_by_name ON users.user_id = user_created_by_name.user_id WHERE users.user_id = 3";
$query=$this->db->query($sql);
If you you have users that were not created by another user use a LEFT JOIN instead:
$sql = "SELECT users.user_id, users.user_name, user_created_by_name.user_name,
FROM users LEFT JOIN users AS user_created_by_name ON users.user_id = users.user_id WHERE user_created_by_name.user_id = 3";
$query=$this->db->query($sql);
This will work:
SELECT a.user_id as User_id,
a.user_name as Name,
b.user_id as Created_by_user_id,
b.user_name as Created_by_name
FROM users AS a
INNER JOIN users AS b
ON a.user_id = b.user_created_by
WHERE a.user_id = 3
It is called a self-join, which is used when combining two records of the same table.

Mysql record count from related tables

I'm trying to get a records count from related tables. I am able to achieve the result I need by breaking the queries and merging the arrays but I know it's inefficient.
I am looking for a cleaner and efficient way of doing this.
All tables are one-to-many. Example:
-One User has many Objects
-One Object has many Items
Table Users
______________
| ID | UID |
______________
| 1 | U1 |
| 2 | U2 |
______________
Table Objects
______________
| ObjID | UID |
______________
| o1 | U1 |
| o2 | U1 |
| o3 | U1 |
______________
Table Items
_________________
| itemID | ObjID |
_________________
| i1 | o1 |
| i2 | o1 |
| i3 | o1 |
| i4 | o1 |
| i5 | o1 |
| i6 | o2 |
_________________
The result I am looking for for U1 is:
| Objects | Items |
| 3 | 6 |
This sql is where I got stuck:
select count(objects.id), count(items.id)
from users
left join Objects on objects.uid = users.uid
left join Items on items.objID = objects.objID
where users.uid = 'U1'
Consider the following:
create table users
(user_id serial primary key,name char(2) unique
);
insert into users values (101,'U1'),(102,'U2');
create table user_objects
(user_id int not null
,object_id int not null
,primary key(user_id,object_id)
);
insert into user_objects values
(101,1),
(101,2),
(101,3);
create table object_items
(object_id int not null
,item_id int not null
,primary key(object_id,item_id)
);
insert into object_items values
(1,1001),
(1,1002),
(1,1003),
(1,1004),
(1,1005),
(2,1001);
select u.name
, count(distinct uo.object_id) objects
, count(oi.item_id) items
from users u
left
join user_objects uo
on uo.user_id = u.user_id
left
join object_items oi
on oi.object_id = uo.object_id
group
by u.user_id;
http://sqlfiddle.com/#!9/d2285/1
Try this: though i am not sure about your item count
select count(objects.id), count(distinct items.id)
from Items inner join Objects on objects.ObjID= users.ObjID
inner join Items on users.uid= objects.uid
where users.uid = 'U1'
One quick way to get the desired result is to use correlated subqueries:
select (select count(*)
from Objects
where UID = 'U1') as Objects,
(select count(*)
from Items as i
inner join Objects as o on i.ObjID = o.ObjID
where o.UID = 'U1') as Items
Another way is to create a derived table of items per object and join to this table to get the total items per user:
SELECT COUNT(*) AS Objects, SUM(cnt_per_oID) AS Items
FROM Users AS u
JOIN Objects AS o ON u.UID = o.UID
LEFT JOIN
(
SELECT ObjID, COUNT(*) cnt_per_oID
FROM Items
GROUP BY ObjID
) AS i ON o.ObjID = i.ObjID
WHERE u.UID = 'U1'
GROUP BY u.UID
Demo here
Try this:
select count(distinct ObjId) Objects,
count(distinct ItemId) Items
from Items i
where exists(select 1 from Objects
where ObjId = i.ObjId and UID = 'U1');

How can I get the name of each user by his id?

I have this query:
SELECT DISTINCT p1.rootid AS user_id, p1.rid AS friend_id
FROM relations p1
WHERE rootid = 1246
OR rootid IN (SELECT p2.rid
FROM relations p2
WHERE rootid = 1246);
The result of it is something like this:
Also I have a table which contains names. Something like this:
// users
+------+_--------+
| id | name |
+------+---------+
| 1246 | Jack |
| 1247 | Peter |
| 1246 | Ali |
| . | . |
| . | . |
| . | . |
+------+---------+
Now I want to get names instead of ids in the output. Noted that both user_id and friend_id refer to users table. How can I do that?
Try something like this,
SELECT u1.name user_name, u2.name friend_name from relations r
INNER JOIN users u1 ON u1.id = r.user_id
INNER JOIN users u2 ON u2.id = r.friend_id
where r.user_id = 1246
Hope this will solve your problem.
Try this :
SELECT DISTINCT u.name AS user_name, f.name AS friend_name
FROM relations p1
INNER JOIN user u ON u.id = p1.rootid
INNER JOIN user f ON f.id = p1.rid
WHERE rootid = 1246
OR rootid IN (SELECT p2.rid
FROM relations p2
WHERE rootid = 1246);

MYSQL Select from 2 tables with an id that is in just in one of them

This is my question:
I have 3 tables:
USERS
USERS_INFO
COMPANIES_INFO
The USERS table has a field ID
The tables USERS_INFO and COMPANIES_INFO have a field ID_USER that is linked by a foreign key with ID.
The question is, how can I select a row that is present in just one of them?
An example:
USERS
+----+
| id |
+----+
| 1 |
+----+
| 2 |
+----+
USERS_INFO
+---------+---------+
| id_user | name |
+---------+---------+
| 1 | Jhonny |
+---------+---------+
COMPANIES_INFO
+---------+---------+
| id_user | company |
+---------+---------+
| 2 | Apple |
+---------+---------+
What I want is something like this:
SELECT * FROM users_info, companies_info WHERE id_user=2
And get this:
id_user = 2
company = Apple
Instead if I did
SELECT * FROM users_info, companies_info WHERE id_user=1
I would have got:
id_user =1
name = Jhonny
I want for example select the user 2, by checking both tables USERS_INFO and COMPANIES_INFO because we don't know which one contains it...
Any help?
Try this:
SELECT U.id
FROM users U
LEFT JOIN users_info UI ON U.id = CI.id_user
LEFT JOIN companies_info CI ON U.id = CI.id_user
WHERE UI.id IS NOT NULL AND CI.id IS NOT NULL
You can try this:
SET #user = 1;
SELECT id_user, name FROM USERS_INFO WHERE id_user = #user
UNION
SELECT id_user, company as name FROM COMPANIES_INFO WHERE id_user = #user;