MySQL JOIN / UNION DISTINCT issue - mysql

I am working on a project where users need to have permissions based on which department they are connected to.
So now I am trying to do a query that will show me users that have all necessary permissions based on the users department_id (users that do not have all necessary permissions shall not be displayed in the result).
I have been trying lot of different things (different type of JOINS, sub queries w/where in, exists and union distinct) without luck, this kind of query is next level for me and challenges my logical thinking.
Tables:
users
id
first_name
last_name
department_id
permissions
id
name
departments (relation to groups and categories based on department)
id
name
category_id
group_id
categories
id
name
groups
id
name
departments_permissions (permission requirement)
department_id
permission_id
categories_permissions (permission requirement)
category_id
permission_id
groups_permissions (permission requirement)
group_id
permission_id
users_permissions (users have permissions here)
user_id
permission_id
Department
A department CAN (not required) have many permissions (departments_permissions).
A department CAN (not required) have a category.
A department CAN (not required) have a group.
Category
A category CAN (not required) have many permissions (categories_permissions).
Group
A group CAN (not required) have many permissions (groups_permissions).
Important side note
departments_permissions, categories_permissions and groups_permissions can contain one or more of the same permission_id's, therefore we have to select distinct values.
Example data
https://pastebin.com/BvhAznpY
My latest query attempt:
SELECT
`users`.id,
`users`.first_name,
`users`.last_name,
`users`.department_id
FROM
`users`
INNER JOIN
`departments` ON `users`.department_id = `departments`.id
INNER JOIN
`users_permissions` ON `users`.id = `users_permissions`.user_id
INNER JOIN
(
SELECT permission_id FROM departments_permissions WHERE department_id = departments.id
UNION DISTINCT
SELECT permission_id FROM categories_permissions WHERE category_id = departments.group_id
UNION DISTINCT
SELECT permission_id FROM groups_permissions WHERE group_id = departments.group_id
) AS tblPermissionsRequired ON `users_permissions`.permission_id = `tblPermissionsRequired`.permission_id
GROUP BY
`users_permissions`.user_id;

Build 2 tables (sub-queries) to list
all the permission a user required (i.e. user_permissions)
all the permission a user have (i.e. 3 unions similar to what you have done)
Join them and group by id would find the the number of required and how many of them are given already.
Finally, add a filter clause (using HAVING). Then you got the list of user ID :)
select
up.user_id,
max(u.first_name) as first_name,
max(u.last_name) as last_name,
count(up.permission_id) as permission_required,
count(perm.permission_id) as permission_have
from
users u
join
users_permissions up on u.id = up.user_id
left join
(
SELECT users.id as user_id, permission_id
FROM users
INNER JOIN departments ON users.department_id = departments.id
INNER JOIN departments_permissions on departments_permissions.department_id = departments.id
UNION
SELECT users.id, permission_id
FROM users
INNER JOIN departments ON users.department_id = departments.id
INNER JOIN categories_permissions on categories_permissions.category_id = departments.category_id
UNION
SELECT users.id, permission_id
FROM users
INNER JOIN departments ON users.department_id = departments.id
INNER JOIN groups_permissions on groups_permissions.group_id = groups_permissions.group_id
) perm
on up.user_id = perm.user_id and up.permission_id = perm.permission_id
group by up.user_id
having count(up.permission_id) = count(perm.permission_id)

I think what you need is something like this:
SELECT u.first_name,u.last_name,p.name
FROM users u, user_permissions up, permissions p
WHERE u.id=up.user_id AND
up.permission_id=p.id
UNION
SELECT u.first_name,u.last_name,p.name
FROM users u, departments d, departments_permission dp, permissions p
WHERE u.department_id=d.id
AND d.id=dp.department_id
AND dp.permission_id=p.id
UNION
SELECT u.first_name,u.last_name,p.name
FROM users u, departments d, categories_permission cp, permissions p
WHERE u.department_id=d.id
AND d.category_id=cp.category_id
AND cp.permission_id=p.id
UNION
SELECT u.first_name,u.last_name,p.name
FROM users u, departments d, groups_permission gp, permissions p
WHERE u.department_id=d.id
AND d.group_id=gp.group_id
AND gp.permission_id=p.id
Here's a hint:
Create a VIEW with the query above
Do a SELECT DISTINCT * on the view

This following script should work-
SELECT A.id,A.first_name,A.last_name,A.department_id
FROM
(
SELECT U.*,
A.Dept_id AS Dept_ID,
A.permission_id AS [Dept_Wise_Permissiion_List],
UP.permission_id
FROM users U
INNER JOIN (
SELECT D.id Dept_id,CP.permission_id
FROM departments D INNER JOIN categories_permissions CP ON D.category_id = CP.category_id
UNION ALL
SELECT D.id Dept_id,GP.permission_id
FROM departments D INNER JOIN groups_permissions GP ON D.group_id = GP.group_id
UNION ALL
SELECT D.id Dept_id, DP.permission_id
FROM departments D INNER JOIN departments_permissions DP ON D.id = DP.department_id
) A ON U.department_id =A. Dept_id
LEFT JOIN users_permissions UP
ON U.id = UP.user_id AND A.permission_id = UP.permission_id
)A
GROUP BY A.id,A.first_name,A.last_name,A.department_id
HAVING COUNT(ISNULL(A.permission_id,1)) = COUNT(A.permission_id)

Related

How to triple JOIN tables to perform a query with OR operand in MySQL [Specific Case]

Hello dear SO community,
I have been struggling with this for some time now and since time is running out I turn to you.
I have these tables:
USERS:
user_id (PK)
user_name
PROJECTS:
project_id (PK)
project_name
user_id (FK) - referring to the creator of the project
Users and projects are in M:N relationship, which is captured in table PERMISSIONS.
PERMISSIONS:
project_id (FK)
user_id (FK)
Permissions row holds a access rule for single user and single project. If a pair for given user and project does not exist in the table, that user cannot access that project.
I am trying to query all projects that current user should have access to, meaning those that he created and those that he is associated with in the PERMISSIONS table but with every project I want the user_name of its creator. I supply current user ID to a prepared statement in PHP, which works fine, but I cannot seem to get the query right. I have been able
I was able to to get projects that were created or accessible by certain user, but I cannot figure out how to join USERS, to get the user_name of user_id in projects (username of the project creator) that I query.
I am querying something along the lines of:
SELECT projects.name as name, projects.project_id as project_id, projects.user_id as user_id, users.name as user_name
FROM projects JOIN permissions USING (project_id) JOIN users USING (user_id) WHERE permissions.user_id=:user_id OR
projects.user_id=:user_id ORDER BY name
There are three possibilities:
Have a join on projects with or, to fetch both creators and those with permission. A join with or is generally not advisable because it leads to table scans. Distinct is needed
select distinct u.user_name, p.project_name
from users u
left join permissions pm on pm.user_id = u.user_id
left join projects p on p.user_id = u.user_id or p.project_id = pm.project_id
where u.user_id = 2;
Cross join projects and have the 'or's in the where clause. Need distinct.
select distinct u.user_name, p.project_name
from users u
cross join projects p
left join permissions pm on pm.user_id = u.user_id
where u.user_id = 2 and ( p.project_id = pm.project_id or p.user_id = u.user_id );
Use union, one select for creators and one for those with permission. Distinct is not needed because union does distinct
select u.user_name, p.project_name
from users u
inner join projects p on p.user_id = u.user_id
where u.user_id = 2
union
select u.user_name, p.project_name
from users u
inner join permissions pm on pm.user_id = u.user_id
inner join projects p on p.project_id = pm.project_id
where u.user_id = 2
online editor here: https://onecompiler.com/mysql/3y64ukxtp
The issue in your query is that the user_id field should be both matched:
between users and permissions, to catch projects the user is allowed to work on
between users and projects, to catch projects the user has created
One way of solving this issue is to have a UNION operation between these two result sets. In order to make the joins more efficient, it's better to move the filtering operation (finding the specific user) before the join operation, that's the reason why I've moved it into a subquery (under the shape of common table expression - cte).
WITH user_details AS(
SELECT user_id,
user_name
FROM users
WHERE user_id = 501
)
SELECT user_details.*,
projects.project_id,
projects.project_name,
projects.user_id AS project_creator
FROM user_details
INNER JOIN projects
ON user_details.user_id = projects.user_id
UNION
SELECT user_details.*,
projects.project_id,
projects.project_name,
projects.user_id AS project_creator
FROM user_details
INNER JOIN permissions
ON user_details.user_id = permissions.user_id
INNER JOIN projects
ON permissions.project_id = projects.project_id
You can try it in a sample environment here.
With this query:
SELECT project_id FROM projects WHERE user_id = :user_id
UNION ALL
SELECT project_id FROM permissions WHERE user_id = :user_id
you get the ids of all projects that :user_id created or has access to.
Use it with the operator IN:
SELECT p.*, u.user_name
FROM projects p INNER JOIN users u
ON u.user_id = p.user_id
WHERE p.project_id IN (
SELECT project_id FROM projects WHERE user_id = :user_id
UNION ALL
SELECT project_id FROM permissions WHERE user_id = :user_id
);

Sql query correction one to many tables

I have two tables one is users and second is user_education.One users can have more than one education listing so i want to get the latest user education listing
users
===============
1-id
2-email
member_experience
==============
1-id
2-user_id
3-designation
user id 1 has 4 enteries in user_education so i want to get the last record enter designation of the user
original full query is like this
SELECT u.id,u.name,u.gender,u.email,file_managed.file_name,file_managed.file_path
from users as u
INNER JOIN member_experience on (SELECT uid FROM member_experience where member_experience.uid=u.id ORDER BY id DESC LIMIT 1)=u.id
LEFT JOIN file_managed on file_managed.id= u.fid
where u.user_type ='individual' AND u.gender='male'
"INNER JOIN member_experience on (SELECT uid FROM member_experience where member_experience.uid=u.id ORDER BY id DESC LIMIT 1)=u.id "
this portion has problem as users has many record in member_experience table but i want to get only one which is latest.
thanks
Devolve the acquisition of the last record to the where statement.
drop table if exists member_experience;
create table member_experience(id int auto_increment primary key, userid int);
insert into member_experience (userid) values
(1),(2),(1);
select * from member_experience
SELECT u.id,m.id
from users as u
join member_experience m on m.userid = u.id
where m.id = (SELECT max(m.id) FROM member_experience m where m.userid = u.id)
order by u.id
Or if you want to include those with no experience
SELECT u.id,m.id
from users as u
left join member_experience m on m.userid = u.id
where (m.id = (SELECT max(m.id) FROM member_experience m where m.userid = u.id)
or m.id is null)
and u.id < 4
order by u.id

MySQL alias a query for use in another query

My title isn't great, but let me explain my situation. I have a jobs table. The jobs table has 2 foreign keys to the users table: sales_rep_id and account_manager_id.
Then I have another table called contact_info with a one to one relationship to the users table.
jobs
-----
sales_rep_id
account_manager_id
...
users
-----
first_name
last_name
contact_info
-----
user_id
home_phone
If I want to do a query where I get the phone number for both people on every job I would do the following:
SELECT reps.home_phone as reps_home, account_managers.home_phone as a_m_home FROM jobs
JOIN
(SELECT * FROM users
JOIN contact_info
ON users.id = contact_info.user_id) reps
ON reps.id = jobs.sales_rep_id
JOIN
(SELECT * FROM users
JOIN contact_info
ON users.id = contact_info.user_id) account_managers
ON account_managers.id = jobs.account_manager_id
Is there anything I can do to create a temporary table with the joined data? What is the most efficient way to do this join? For example, what if I had 10 foreign keys in the jobs table to the users table, and I needed the phone_number for all 10?
This should be obtain all the info you need
SELECT j.*
, u1.first_name as sales_rep_first_name
, u1.last_name as sales_reps_last_name
, u2.first_name as manager_first_name
, u2.u1.last_name as manager_last_name
, c1.home_phone as sales_rep_home_phone
, c2.home_phone as manager_home_phone
FROM jobs as j
INNER JOIN contact_info as c1 ON j.sales_rep_id = c1.user_id
INNER JOIN user u1 ON u1.id= c1.user_id
INNER JOIN contact_info as c2 ON j.saccount_manager_id = c2.user_id
INNER JOIN user u2 ON u2.id= c2.user_id;
Define a view that joins user and contact_info.
CREATE VIEW user_contact_info
SELECT u.id, u.first_name, u.last_name, c.home_phone
FROM user AS u
JOIN contact_info AS c ON u.id = c.user_id
Then you can use this as if it's a table.
SELECT reps.home_phone as reps_home, account_managers.home_phone as a_m_home
FROM jobs
JOIN user_contact_info AS reps ON reps.id = jobs.sales_rep_id
JOIN user_contact_info AS account_managers ON account_managers.id = jobs.account_manager_id

Joining three tables where the term is an exclusion

I have a query that involves 3 tables. The table "project" contains the records I want to retrieve.
3 tables:
project
participants:
id
project_id (refers to id of table 1)
usertable_id (refers to is of table 3)
usertable:
id
lastname
user_type (either "active" or "inactive")
I want to retrieve all the projects (table 1) where the participants (table 2) are NOT of user_type = 'inactive' (table 3)
What is the correct query to join these three tables so that only these projects are retrieved?
select * from projects p
left join participants ps on ps.project_id = p.id
left join usertable u on ps.usertable_id = u.id
where u.user_type <> 'inactive'
Do a simple join query with your where filter
SELECT p.*
FROM project p
LEFT JOIN participants pr ON(p.id = pr.project_id)
LEFT JOIN usertable u ON(u.id=pr.usertable_id )
WHERE u.user_type <> 'inactive'
Edit from comments
SELECT p.*
FROM project p
WHERE NOT EXISTS
(
SELECT 1
FROM participants pr
JOIN usertable u ON(u.id=pr.usertable_id )
WHERE u.user_type = 'inactive'
AND pr.project_id= p.id
)

Collecting data from 3 tables into 1 query

I have three different tables in my MySQL database.
table users: (id, score, name)
table teams: (id, title)
table team_Members: (team_id, user_id)
What I would like to do is to have 1 query that finds every team ID a given user ID is member of, along with the following information:
total number of members in that team
the name of the team
users rank within the team (based on score)
EDIT:
Desired output should look like this;
TITLE (of the group) NUM_MEMBERS RANK
------------------------------------------------
Foo bar team 5 2
Another great group 34 17
.
.
.
The query should be based on the users ID.
Help is greatly appreciated
I think this query should get what you ask for
select t.id, t.title, count(m.user_id) members, (
select count(1)
from users u3
inner join team_Members m3 on u3.id = m3.user_id
and m3.team_id = t.id and u3.score > (
select score from users where name = 'Johan'
)
) + 1 score
from teams t
inner join team_Members m on t.id = m.team_id
where t.id in (
select team_id
from team_Members m2
inner join users u2 on m2.user_id = u2.id
where u2.name = 'Johan'
)
group by t.id, t.title
To collect you just need use JOIN
SELECT
u.*, t.*, tm.*
FROM
users u
JOIN
team_Members tm ON u.id = tm.user_id
JOIN
teams t ON t.id = tm.team_id;
To get total of number of that team use COUNT with some group key
Some like that
SELECT
COUNT(t.id), u.*, t.*, tm.*
FROM
users u
JOIN
team_Members tm ON u.id = tm.user_id
JOIN
teams t ON t.id = tm.team_id GROUP BY t.id;
And to rank just:
SELECT
COUNT(t.id) as number_of_members, u.*, t.*, tm.*
FROM
users u
JOIN
team_Members tm ON u.id = tm.user_id
JOIN
teams t ON t.id = tm.team_id
GROUP BY t.id
ORDER BY u.score;