Remove duplicate form select result - mysql

#edited
Ok. I will delete all role permissions and add them again.
Is it possible to combine all 4 queries into one?
I am creating a permission system.
Assumptions:
Each user can have more than one role
Each role can have more than one permission
Permissions can also be assigned directly to the user (they have a higher priority than permissions for roles)
The priority of permissions is:
role permission
denial of role
user permission
denied to user
Denying the user has the highest priority
The matter in PHP is quite simple:
I create an array with all permissions
I am getting permissions for a role (order by access)
I assign access, if it's denied, I overwrite access with denied
I do the same for user permissions
I assign access, if it's denied, I overwrite access with denied
This way I have the whole array with permissions for a specific user, e.g. $ user['permission']['delete_post'] // output: false || true
I need to do permission inspection now. This means which user has access to e.g. 'delete_post'
I have this database scructure:
Here fiddle with database: DB fiddle
I have problem with first query:
**Query #1**
=============================================
List of all roles related to permission with id 3 */
SELECT DISTINCT role_id, access, permission_id FROM role_permissions WHERE permission_id=3 ORDER BY role_id, access DESC;
| role_id | access | permission_id |
| ------- | ------ | ------------- |
| 5 | 0 | 3 |
| 8 | 1 | 3 |
| 10 | 1 | 3 |
| 10 | 0 | 3 |
As expected I should get
| role_id | access | permission_id |
| ------- | ------ | ------------- |
| 8 | 1 | 3 |
I cant add WHERE permission_id=3 AND access=1, because i getting result: role_id=8 and role_id=10, but role_id=10 doesn't really have access.

One way is to do all appropriate joins between various tables based on their relationships, and then do aggregation based filtering using GROUP BY with HAVING clause.
Following query will give you all the users who has access allowed for a given permission id (more explanation in comments inside the query below - there may be requirement of more fiddling with the logic; check and comment if needed):
Query - View on DB Fiddle
SELECT
u.user_id,
u.name
FROM users AS u
-- Left Join to get access (if defined for input permission id)
LEFT JOIN user_permissions AS up
ON up.user_id = u.user_id
AND up.permission_id = 3
-- Join to Roles; assuming every user has atleast one role
-- Change this to LEFT JOIN if it is possible that user can have NO role
JOIN user_roles AS ur
ON ur.user_id = u.user_id
-- Left Join to get access defined for input permission id for roles
LEFT JOIN role_permissions AS rp
ON rp.role_id = ur.role_id
AND rp.permission_id = 3
GROUP BY u.user_id, u.name
HAVING
-- first priority if user specific access allowed
-- if data is sane then up.access will have same value
-- across all rows in a group for user
MAX(up.access) = 1
OR
-- second priority is not have a single role with
-- denied permission AND
-- atleast one role exists with allowed permission
( NOT SUM(rp.access = 0)
AND
SUM(rp.access = 1)
);
Result
| user_id | name |
| ------- | ---------------- |
| 4 | Cyrus Gomez |
| 7 | MacKensie Morton |
| 13 | Nadine Taylor |
| 15 | Ezekiel Bonner |
| 17 | Ciaran Turner |
| 35 | Olga Dominguez |
| 38 | Lucas Pierce |

You can add a WHERE condition as below-
SELECT DISTINCT role_id, access, permission_id
FROM role_permissions
WHERE permission_id=3
AND role_id NOT IN (
SELECT DISTINCT role_id
FROM role_permissions
WHERE access = 0
AND permission_id=3 -- New condition added
)
ORDER BY role_id, access DESC;

Related

Mysql select distinct user with each of their own preference

Let's say I have a user and preference table, as well as a bridge table user_preference between the two:
/* user table: */
+----------+--------------+
| Field | Type |
+----------+--------------+
| id | int |
| username | varchar(255) |
+----------+--------------+
/* preference table: */
+------------+--------------+
| Field | Type |
+------------+--------------+
| preference | varchar(255) |
+------------+--------------+
/* user_preference table: */
+-----------------+--------------+
| Field | Type |
+-----------------+--------------+
| user_id | int |
| preference_name | varchar(255) |
+-----------------+--------------+
For instance there are 3 preferences to choose from: "swimming", "watching TV", "cycling". And one user can have zero or all 3 of the preferences, which is reflected on the user_preference table.
Now I want to query 10 different users, and with all of them each of their own preferences included, either null or mutiple preferences, how to construct a select statement for that?
So far I have tried something like this:
SELECT u.*, p.preference_name
FROM user u
LEFT JOIN user_preference p ON p.user_id = u.id
LIMIT 10;
/* Result: */
id | username | preference_name
1 | user1 | swimming
1 | user1 | cycling
2 | user2 | null
3 | user3 | watching TV
... /* rest of the result */
As you can see the result will return a duplicate user1, and it won't be 10 distinct users. I'm aware of the distinct and group by keywords, it doesn't solve the problem, as it will only return a single preference for a user, while the user can have multiple preferences.
How to do that with one single select statement?
Try this.
SELECT u.*,
GROUP_CONCAT(DISTINCT p.preference_name) AS prefs
FROM user u
LEFT JOIN user_preference p ON p.user_id = u.id
GROUP BY u.id
LIMIT 10;
The GROUP_CONCAT() will make a comma-separated list of preferences for each user.
Pro tip. When tables get very large, altering ENUMs to add more values gets very time-consuming. Plus, it's usually unwise to design a database so it needs lots of ALTER TABLE statements as it grows. So, the approach you have outlined is the right way to go if you want your possible preferences to be open-ended.

what is the best mysql database model for users with permissions [duplicate]

This question already has answers here:
How important are lookup tables?
(7 answers)
Closed 2 years ago.
i have a table of users where each user should have a list of permissions.
i have red in another answer that i should use a second table called permissions with a user id to assign the permission to a specific user.
userID | permission
--------|----------
1 | read
1 | write
1 | delete
with this solution i can just select all permissions with the id of the user and get all the permissions of a specific user.
what i think to be inefficient about this is the fact that all permissions will repeat for different users in this table.
userID | permission
--------|----------
1 | read
1 | write
1 | delete
2 | write <- repeating with different userid
in my case i will have at least 150 users and probably more than 100 permissions
which will make the table kind of big ?
i would guess this is a very common case to have. but i could not find a better way.
You can just create a new table with the list of all possible permissions and link those ID's with the User ID's.
- Permissions ( ID | Description )
- User Permissions ( UserId | PermissionID )
To select all the permissions for a single User
SELECT Permissions.*
FROM UserPermissions
LEFT JOIN Permissions ON Permissions.id = UserPermissions.PermissionId
WHERE UserPermissions.UserID = {userId}
Why do not make third table like this?
userID | other columns for users
--------|-------------------------
1 |
2 |
3 |
userID | permissionID
--------|----------
1 | 1
1 | 2
1 | 3
2 | 2
permissionID | permissionName
--------------|----------
1 | read
2 | write
3 | delete

MySQL: Grouping and counting multiple occurences, then only display the highest amount per individual combination

I'm currently trying to determine which user owns which PC in my company.
We're logging every login activity in a MySQL table.
Now we only have information about which User is using which client, but given that users may also use other machines, I need to check which user has the highest amount of logins on that specific machine.
Table: log
+----+-----------+------+----------------+
| ID | Client | user | timestamp |
+----+-----------+------+----------------+
| 1 | hostnamea | ab | xx.xx.xx xx:xx |
| 2 | hostnameb | cd | xx.xx.xx xx:xx |
| 3 | hostnameb | ab | xx.xx.xx xx:xx |
| 4 | hostnameb | ab | xx.xx.xx xx:xx |
| 5 | hostnamec | ab | xx.xx.xx xx:xx |
| 6 | hostnamec | ef | xx.xx.xx xx:xx |
| 7 | hostnamec | ef | xx.xx.xx xx:xx |
+----+-----------+------+----------------+
(timestamp is actually unimportant,
but maybe someone has an awesome optimization idea which includes the timestamp,
so I leave it here)
In this case, hostname user "ef" becomes owner of "hostnamec"
I already got so far that I can display a "top logins" list:
SELECT `user`, `client`, COUNT(*) logins
FROM `log`
WHERE `client` LIKE '_%' AND timestamp > "2016-09-00 00:00:00" # (filter out garbage and old machines)
GROUP BY `client, `user`
HAVING COUNT(*) > 10 (If there are less than 10 logins, the machine isn't being used anyways)
ORDER BY `user`;
This returns user, client and a number of logins, which, I hope, specify how often a specific user logged into a machine.
I don't really know how to access and use the extra "logins" row generated by the count.
My goal is something like this:
+-----------+------+
| Client | user |
+-----------+------+
| hostnamea | ab | (1xab, nothing else: ab wins)
| hostnameb | ab | (2x ab,1x cd: ab wins)
| hostnamec | ef | (2x ef, 1x ab: ef wins)
+-----------+------+
My next step would be an Update on another existing table that has hostnames, but it's missing users
UPDATE hosts
INNER JOIN query(output of first query) USING (client)
SET hosts.user = query.user
(If that makes sense)
Can anyone assist me in my first query?
I already found some solutions including Oracle functions, or tables that are slightly diffently designed.
It's been long time since I had to do more than simple Selects, updates and inserts with SQL; to be honest, I got kinda confused by what I found, so I'm asking my own question
You can do this by filtering in the where clause. Here is one way:
select l.*
from log l
where l.timestamp = (select max(l2.timestamp)
from log l2
where l2.user = l.user
);
This is easily generalizable to get the owner as of any given date:
select l.*
from log l
where l.timestamp = (select max(l2.timestamp)
from log l2
where l2.timestamp <= $AsOfDate and l2.user = l.user
);
Try this query
SELECT
Client
, SUBSTRING_INDEX(GROUP_CONCAT(user ORDER BY Client DESC), ',', 1) user
FROM
log
GROUP BY
Client
ORDER BY
Client ASC

How to write a proper If...Else Statement with JOIN in MySQL?

I'm quite a beginner in MySQL I just know the totally basic statements, however now I'ts time for me to get into some more difficult, but worth stuff.
I actually have 3 tables in MySQL, here is the representation:
users:
user_id | name | country
---------------------------
1 | Joseph | US
2 | Kennedy | US
3 | Dale | UK
admins:
admin_id | name | country
----------------------------
1 | David | UK
2 | Ryan | US
3 | Paul | UK
notes:
id | n_id | note | comment | country | type | manager
----------------------------------------------------------------
1 | 3 | This is the 1st note | First | US | admin | 2
2 | 2 | This is the 2nd note | Second | US | user | 1
3 | 2 | This is the 3rd note | Third | UK | user | 2
Now I would like to execute something like this SQL (I'm going to type not real commands here, because I'm not really familiar with all of the SQL expressions):
IF notes.type = admin
THEN
SELECT
notes.note,
notes.comment,
notes.country,
admins.name,
admins.country
FROM notes, admins
WHERE notes.n_id = admin.admin_id
ELSEIF notes.type = 'user'
SELECT
notes.note,
notes.comment,
notes.country,
users.name,
users.country
FROM notes, users
WHERE notes.n_id = users.user_id
I hope you understand what would I like to achieve here. I could do this easily with more SQL statements, but I would like to try some query which doesn't use that much resources.
Edit 1:
I would like to Get all of the Notes and get which usergroup has submitted it than apply the user's name to it. I mean, if the admin submitted the note, than SQL should choose the ID from the Admin table (as per the type value) but if a User submitted the note, it should get the name from the Users table.
The result should look something similar to this:
result:
------
id | note | comment | country | name
--------------------------------------------------------
1 | This is the 1st note | First | US | Paul
2 | This is the 2nd note | Second | US | Kennedy
3 | This is the 3rd note | Third | UK | Kennedy
Edit 2:
I have actually forgot to mention, that all of these should be listed to a manager. So a 'manager ID' should be added to the Notes and list all of the notes where the manager is for example: 2.
Here is a method that you can do in one query:
SELECT n.note, n.comment, n.country,
coalesce(a.name, u.name) as name, coalesce(a.country, u.country) as country
FROM notes n left join
admins a
on n.n_id = a.admin_id and n.type = 'admin' left join
users u
on n.n_id = u.user_id and n.type = 'user';
This uses left join to bring the records together from both tables. It then chooses the matching record for the select.
To select a particular manager, remove the semicolon and add:
where n.manager = 2;
If you expect admins and users in one result you have got several options. The simplest way is to make a union select like this:
SELECT
notes.note,
notes.comment,
notes.country,
admins.name,
admins.country
FROM
notes join admins on notes.n_id = admin.admin_id
WHERE
notes.manager = 2
UNION ALL
SELECT
notes.note,
notes.comment,
notes.country,
users.name,
users.country
FROM
notes join users on notes.n_id = users.user_id
WHERE
notes.manager = 2

User System - Multiple Roles in MySQL Database

So I am in the process of attempting to create a basic user system and within this system I want users to be able to have multiple roles.
Say for example I have the roles as follows: Administrator, Events Organiser, Donator
What is the best way to assign these multiple roles to a user and then check if they have any of these roles for showing certain permissions.
If it was only one role per person then it wouldn't be a problem as I'd just assign say Administrator = 10, Organiser = 5 and Donator = 1 and then do an if statement to check if the MySQL data is equal to any of those three numbers.
I can't imagine there is a way to add a MySQL Field and fill it with say "Administrator,Donator" and therefore that user would have both of those roles?
Is it just a case of I would need to create 3 separate fields and put a 0 or a 1 in those fields and check each one separately?
Use multiple tables and join them:
User
--------------
id name
1 test
Role
--------------
id name
1 Donator
2 Organizer
3 Administrator
User_Role
--------------
id user_id role_id
1 1 1
2 1 3
SELECT * FROM User u
LEFT JOIN User_Role ur ON u.id = ur.user_id
LEFT JOIN Role r ON ur.role_id = r.id
WHERE r.name = "Administrator";
The query is easier if you know you only have 3 roles and they are easy to remember.
SELECT * FROM User u LEFT JOIN User_Role ur ON u.id = ur.user_id WHERE ur.role_id = 3;
You will have a roles, users and users_roles tables:
The roles table will hold the various roles your users can have. In my example data I've declared Administrator and Donator roles.
roles
id unsigned int(P)
description varchar(15)
+----+---------------+
| id | description |
+----+---------------+
| 1 | Administrator |
| 2 | Donator |
| .. | ............. |
+----+---------------+
And of course you'll have to store information about your users.
users
id unsigned int(P)
username varchar(32)
password varbinary(255)
etc.
+----+----------+----------+-----+
| id | username | password | ... |
+----+----------+----------+-----+
| 1 | bob | ******** | ... |
| 2 | mary | ******** | ... |
| .. | ........ | ........ | ... |
+----+----------+----------+-----+
Finally you'll tie the two together in the users_roles table. In my example data you can see that bob is a Donator and mary is both an Administrator and a Donator. The user_id and role_id are both foreign keys to their respective tables and together they form the primary key for this table.
users_roles
user_id unsigned int(F user.id)\_(P)
role_id unsigned int(F role.id)/
+----+---------+---------+
| id | user_id | role_id |
+----+---------+---------+
| 1 | 1 | 2 |
| 2 | 2 | 1 |
| 3 | 2 | 2 |
| .. | ....... | ....... |
+----+---------+---------+
This way a user can have an unlimited number of roles.
If you don't want to create two new table for such small thing.
You can utilize MySQL built in SET data type or you can store comma separated role in varchar and do query operation using FIND_IN_SET()
Eg.
SELECT * FROM user WHERE FIND_IN_SET('admin', role)>0;
+----+----------+----------+----------------+
| id | username | password | role (varchar) |
+----+----------+----------+-----------------+
| 1 | bob | ******** | admin |
| 2 | mary | ******** | admin,role1 |
| .. | ........ | ........ | role1,role2 |
+----+----------+----------+-----------------+