1

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;
Andrew Larsen
  • 1,257
  • 10
  • 21
  • 2
    Sample data from different table with your expected output would help. – mkRabbani Aug 01 '19 at 11:08
  • @mkRabbani I've updated my question (see pastebin url). – Andrew Larsen Aug 01 '19 at 11:32
  • You don't clearly say in detail what the query should return. When does a user "have all necessary permissions based on the users department_id"? What exact condition is "based on" vaguely summarizing? PS Please put everything needed for your question in your question as cut & paste & runnable text. [mre] PS You give some aspects of your situations/states, namely some constraints on table values, but you need to tell us for every table (base & query result) what a row in it says about the situation/state in terms of its column values. – philipxy Aug 02 '19 at 04:39
  • @philipxy I want to retrieve rows from the users table (look at my latest query attempt, specifically the select part). A user has all necessary permissions, when users_permissions contains all the permissions that are related to the user through department_id -> departments -> departments_permissions, categories_permissions, groups_permissions. There may or may not be permission requirements in any of the permission tables. If there are any, match all distinct permissions with users permissions. Look at the pastebin url to find example data and example of expected output. – Andrew Larsen Aug 02 '19 at 11:37
  • A [mre] includes cut & paste & runnable code; example input with desired & actual output (including verbatim error messages); tags; clear specification & explanation. That includes the least code you can give that is code that you show is OK extended by code that you show is not OK. (Debugging fundamental.) – philipxy Aug 02 '19 at 18:52
  • Your comment is clearer, good. Please clarify via edits, not comments. PS I just said, put all code/data in your post. (Give the link too.) I also said, it is not necessary to give/know constraints to query. ("There may or may not be [...] If [...]"). PS "related to the user through" & "->" are more unhelpful/impeding vague phrasing. Give the exact condition. Fix/finish: Row (uid, ...) is in the result when/iff for some values for u.*, d.*, ..., user u.uid is in dept u.d_id & ... dept d.id is in category d.c_id & ... uid=u.id & either ... or ... or ... etc. – philipxy Aug 02 '19 at 19:02
  • I disagree with you. There are multiple people that understood this question just fine, what I wrote in this comment when explaining to you is pretty much the same I wrote in my question if you read the question carefully. And one more thing, if you see my query attempt it is pretty clear that I am trying to get some specific columns from the users table. Why the need to specify it when I already have in my query attempt? No wonder some people feel it's troubling to ask questions here, when there allways have to be the one guy. If I just could call this constructive criticism.. – Andrew Larsen Aug 02 '19 at 21:15

3 Answers3

1

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
ChrisFNZ
  • 597
  • 1
  • 4
  • 21
1

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)
Patrick
  • 234
  • 1
  • 7
  • This looks good! Thanks! Will test it and give feedback. May I ask why you use max(u.first_name) ? – Andrew Larsen Aug 01 '19 at 13:14
  • 1
    We need to group by user_id, therefore we will need to add aggregation functions (count, max etc.) to display other columns not grouped. The alternative is to change grouping on everything. e.g. "group by up.user_id, u.first_name, u.last_name", then you don't need to use max – Patrick Aug 01 '19 at 13:16
  • This works very well @Patrick. It also works if I wan't to do an opposite check by changing the having comparison at the end to "having count(up.permission_id) != count(perm.permission_id)". However when I do an opposite check it will ignore users that doesn't have any permissions in the users_permissions table. Tried to left join the first join to keep all users, but it didn't help. Any suggestions on this case? – Andrew Larsen Aug 07 '19 at 15:27
1

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)
mkRabbani
  • 16,295
  • 2
  • 15
  • 24
  • Ola has department_id 2 in the example data. So if we look at departments_permissions Ola also need permission 4 (so, 1, 3 & 4). Thanks for the answer, I'll test it in a little while. – Andrew Larsen Aug 01 '19 at 13:10
  • How Ola need: 4, 3, 1? Ola's dept is 2 and in departments_permissions table there are only entry for dept 2 where permission id = 4. – mkRabbani Aug 01 '19 at 13:22
  • Ola's department is 2. Department 2 have category 1 and group 2. Ola need permission 4 from departments_permissions and permission 1 from groups_permissions and permission 3 from category_permissions. – Andrew Larsen Aug 01 '19 at 13:29
  • 1
    Got your point. I have updated the script and it should work now. – mkRabbani Aug 01 '19 at 13:45