Include admin role in users table from roles table - sql

Is there a way to query users table like this:
| id | username |
-----------------
| 1 | user1 |
| 2 | user2 |
| 3 | user3 |
and user_roles table:
| id_user | id_role |
---------------------
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
| 3 | 1 |
assuming that role with id = 1 is an admin role, to produce result that looks like this:
| id | username | admin |
-------------------------
| 1 | user1 | Y |
| 2 | user2 | N |
| 3 | user3 | Y |
I think it can be done using nested SELECT statements, but I was wondering if it's doable using JOIN.
Edit:
The admin column value doesn't have to be Y or N, it can be admin role id (1) or NULL or whatever that will let me know if user is an admin

I would try this:
select u.id, u.username, if (id_role is null, 'N', 'Y') as is_admin
from users u
left outer join user_roles r
on u.id = r.id_user and r.id_role = 1
But I'm not 100% sure.

Well, you can join like this, which will give you the id_role in the result.
SELECT u.*, r.id_role
FROM users u
LEFT JOIN user_roles r
ON u.id=r.id_user
You can add WHERE r.id_role=1 to get just the admins, etc.
But to get the admin as "Y" or "N" as you wanted, you can use an IF on whether the id_role is 1 or not.
SELECT u.*, IF(r.id_role = 1, "Y", "N") as admin
FROM users u
LEFT JOIN user_roles r
ON u.id=r.id_user

select u.id_user, if(count(1) > 0, 'Y', 'N')
from user u left outer join user_roles ur on u.user_id = ur.user_roles
where ur.id_role=1
group by u.id_user
I'm not using mysql, so just googled it has the 'if' function, if that's wrong, replace with DECODE, COALESCE or alike.

Related

Returning one result per id in SQL in a joined table

I have one table of users.
users
| user_id | name |
| ------- | ------ |
| 1 | Jerry |
| 2 | George |
| 3 | Elaine |
| 4 | Kramer |
I have one table that links roles to users, and roles are assigned at a tree.
user_roles
| user_id | role_id | tree_id |
| ------- | ------- | ------- |
| 1 | 5 | 1 |
| 1 | 5 | 2 |
| 2 | 6 | 1 |
| 3 | 7 | 1 |
| 4 | 8 | 1 |
I need to only return results where a user's role is assigned at a certain tree_id, so I'm checking all the roles and trees. At the end I want it to return one row per user.
I'm using Knex and doing a query that looks like:
knex('users')
.leftJoin('user_roles', {'user.user_id': 'user_roles.user_id'})
.whereIn('user_roles.tree_id', arrayOfTreeIds)
.andWhere(moreFilters)
SELECT *
FROM users
LEFT JOIN user_roles on users.user_id = user_roles.user_id
WHERE user_roles.tree_id in (1, 2, 3)
I'm getting five results back instead of four, though. If I try to SELECT DISTINCT it tells me I need to GROUP BY, but I can't get that to work. What do I need to do to get only one result per user id?
You have a user that matches on two different tree_ids, so this multiplies the rows.
In pure SQL, you could use exists instead of a join:
SELECT *
FROM users u
WHERE EXISTS (
SELECT 1
FROM user_roles ur
WHERE ur.user_id = u.user_id AND ur.tree_id in (1, 2, 3)
)
Another option is aggregation:
SELECT u.*
FROM users u
INNER JOIN user_roles ur on u.user_id = ur.user_id
WHERE ur.tree_id in (1, 2, 3)
GROUP BY u.user_id
I changed the LEFT JOIN to an INNER JOIN, because that's, in essence, what you want (and what your original query does).
You can even list the matched roles with string aggregation:
SELECT u.*, STRING_AGG(ur.tree_id::text, ',' ORDER BY ur.tree_id) tree_ids
FROM users u
INNER JOIN user_roles ur on u.user_id = ur.user_id
WHERE ur.tree_id in (1, 2, 3)
GROUP BY u.user_id
Disclaimer: I don't know how to write this in knex!
Demo on DB Fiddle

Mark rows from one table where value exists in join table?

For my query, I have two tables, as defined below:
permissions table:
| permission_id | permission_description |
|---------------|------------------------|
| 1 | Create User |
| 2 | Edit User |
| 3 | Delete User |
users_permissions table:
| permission_id | user_id |
|---------------|---------|
| 1 | 1 |
| 1 | 2 |
| 3 | 2 |
| 3 | 5 |
| 1 | 3 |
| 3 | 1 |
Now, I need to retrieve a list of all permissions in the permissions table, with a column to indicate if the user with user_id of 1 exists for each permission in the users_permissions table.
So, my desired output for the query would be:
| permission_id | permission_description | has_permission |
|---------------|------------------------|----------------|
| 1 | Create User | TRUE |
| 2 | Edit User | FALSE |
| 3 | Delete User | TRUE |
So far, I have tried the following query, but it returns entries for all permissions and all user_id values:
SELECT permissions.permission_id,
permission_description,
CASE WHEN user_id = 1 THEN 'TRUE' ELSE 'FALSE' END AS has_permission
FROM permissions
INNER JOIN users_permissions ON permission.permission_id = users_permissions.permissions_id;
How do I limit that to just one entry per permission?
For clarity, the end goal is to get a list of available permissions and mark the ones the user already has.
If you only want to know the answer for one user then an exists subquery will do the job - no need for a join.
SELECT P.permission_id
, P.permission_description
, CASE WHEN exists (select 1 from users_permissions UP where UP.permission_id = P.permission_id and UP.user_id = 1) THEN 'TRUE' ELSE 'FALSE' END AS has_permission
FROM [permissions] P
PS - I wouldn't recommend having a table called permissions as its a reserved word in SQL Server.
Use LEFT JOIN instead of INNER JOIN and check if it is null
select permissions.permission_id,
permission_description,
case when user_id is null then 'FALSE' else 'TRUE' END as has_permission
FROM permissions
LEFT JOIN users_permissions
ON permission.permission_id = users_permissions.permissions_id and user_id = 1

Select latest record on many to many relationship

I have a many to many relationship like below. A user can change their role, and I want to get all users with their last role equals to role2 (example).
users
user_id | user_name | user_password
1 | user1 | *hashed password*
2 | user2 | *hashed password*
3 | user3 | *hashed password*
roles
role_id | role_name | role_description
1 | role1 | *description*
2 | role2 | *description*
user_roles
user_roles_id | user_id | role_id | created_at
1 | 1 | 1 | 2018-04-10 01:01:01
2 | 2 | 2 | 2018-04-10 01:01:02
3 | 3 | 1 | 2018-04-10 01:01:03
4 | 1 | 2 | 2018-04-12 01:01:01
5 | 1 | 1 | 2018-04-13 01:01:02
6 | 2 | 1 | 2018-04-14 01:01:01
7 | 3 | 2 | 2018-04-14 01:01:02
8 | 2 | 2 | 2018-04-15 01:01:01
9 | 1 | 2 | 2018-04-15 01:01:02
10 | 1 | 1 | 2018-04-16 01:01:01
From those tables, I want to get things like
user_id | role_id
2 | 2
3 | 2
But all this time I got
user_id | role_id
1 | 2
2 | 2
3 | 2
user1 should not be there because its last role was role1.
So what I'm trying to do is, get users where last role = 2.
Any help would be appreciated! Thanks in advance!
Addition
The results that I wanted is all data from users table. So it might be like
user_id | user_name | <and all of the rest>
2 | user2 | ...
3 | user3 | ...
So that, the field role_id I mentioned above is just reference to select the user.
This is one solution. Essentially, you are looking at each user's latest role, and only include it in the output, if the role is 2.
SELECT
ur.user_id, ur.role_id
FROM
user_roles ur
WHERE
ur.created_at = (SELECT MAX(created_at)
FROM user_roles
WHERE user_id = ur.user_id)
AND ur.role_id = 2
GROUP BY
ur.user_id, ur.role_id;
EDIT
Based on the additional information from the comments, the following will return all information from the Users table:
SELECT
u.*
FROM
users u INNER JOIN (
SELECT
ur.user_id
FROM
user_roles ur
WHERE
ur.created_at = (SELECT MAX(created_at)
FROM user_roles
WHERE user_id = ur.user_id)
AND ur.role_id = 2
GROUP BY
ur.user_id, ur.role_id) tmp ON tmp.user_id = u.user_id;
Use subquery with correlation approach and do the joins
select u.user_id, u.user_name, u.user_password,
r.role_id, r.role_name, r.role_description
from (select t.user_id, (select role_id from user_roles
where user_id = t.user_id
order by created_at desc LIMIT 1) as role_id
from user_roles t
group by t.user_id) tt
inner join users u on u.user_id = tt.user_id
inner join roles r on r.role_id = tt.role_id
where r.role_id <> 1;

Conditional testing in Oracle SQL

I am trying to write query on a tables which query record based on some condition plus I need one column as status which should checking matching record on another table and set value based on that test.
For example, we have these two tables:
| USER_ID | FIRST_NAME | LAST_NAME | CURR_STATUS |
--------------------------------------------------
| 1234567 | ABC | DEF | ACTIVE |
| 8910111 | GHI | JKL | INACTIVE |
| 2131415 | MNO | PQR | INACTIVE |
| 1617181 | STU | VWX | ACTIVE |
| 9202122 | YZA | BCD | ACTIVE |
--------------------------------------------------
Table: USER
| USER_ID | DOC_NAME |
---------------------
| 1234567 | EFG.TXT |
| 1617181 | HIJ.PDF |
----------------------
Table: USER_DOC
> select first_name, last_name, curr_status
//TODO some sql code for HAS_DOC logic
from USER
where CURR_STATUS = 'ACTIVE';
| FIRST_NAME | LAST_NAME | CURR_STATUS | HAS_DOC |
--------------------------------------------------
| ABC | DEF | ACTIVE | YES |
| STU | VWX | ACTIVE | YES |
| YZA | BCD | ACTIVE | NO |
--------------------------------------------------
Result
Right now I am doing this in Java code with two separate SQL calls:
First to get the all record matching record from USER table.
Then calling second query in the for loop
select user_id from user_doc from user_id = :userId;
I am trying to see if it is can be done with single SQL query and it is faster.
Any suggestion?
One possible solution is to use the EXISTS operator:
SELECT u.first_name, u.last_name, u.curr_status
FROM user u
WHERE u.curr_status = 'ACTIVE'
AND EXISTS (SELECT d.doc_name FROM user_doc d WHERE d.user_id = u.user_id);
If you need more information (say, a document count), you may also try
SELECT u.first_name, u.last_name, u.curr_status, COUNT(d.doc_name) AS num_documents
FROM user u LEFT JOIN user_doc d ON d.user_id = u.user_id
WHERE u.curr_status = 'ACTIVE'
GROUP BY u.first_name, u.last_name, u.curr_status
HAVING num_documents > 0;
One last alternative would be to use the IN operator
SELECT u.first_name, u.last_name, u.curr_status
FROM user u
WHERE u.curr_status = 'ACTIVE'
AND u.user_id IN (SELECT d.user_id FROM user_doc d);
EDIT The "HAS_DOC" attribute can be derived from the count, for example
-- Gets all users
SELECT u.first_name, u.last_name, u.curr_status, COUNT(d.doc_name) AS num_documents
FROM user u LEFT JOIN user_doc d ON d.user_id = u.user_id
WHERE u.curr_status = 'ACTIVE'
GROUP BY u.first_name, u.last_name, u.curr_status;
and in your Java code:
boolean hasDoc = resultSet.getInt("num_documents") > 0;
I recommend something like this. (EDIT: tested, and it works)
select u1.first_name, u1.last_name, u1.curr_status,
nvl2(u2.user_id, 'YES', 'NO') as has_doc
from USER u1
left join (select u.user_id
from USER u
where u.CURR_STATUS = 'ACTIVE'
and exists (
select null
from user_doc ud
where ud.user_id = u.user_id)
) u2 ON u1.user_id = u2.user_id
where u1.CURR_STATUS = 'ACTIVE';

Different User, friendships and selection

I have two tables:
User:
+----+----------+--+
| | User | |
+----+----------+--+
| pk | email | |
| | password | |
| | ... | |
+----+----------+--+
and Friendship:
+----+-------------+--+
| | FriendShip | |
+----+-------------+--+
| pk | user1_email | |
| pk | user2_email | |
| | date | |
| | accepted | |
+----+-------------+--+
So basically, when someone wants to befriend someone else, we record it in the friendship table.
Let's say now that we want to select all the user with less than 3 friends, it is easy to that except for people who does not have friends because there is no record of that in the friendship table.
My query looks like this for the moment:
SELECT u.email, COUNT(u.email)
FROM user u
INNER JOIN friendship f ON f.user1_email = u.email OR f.user2_email = u.email
GROUP BY u.email
HAVING COUNT(u.email) < 3;
How can I add the result of the asocial people :p?
You could use a UNION to quickly get what you want.
SELECT u.email, COUNT(u.email)
FROM user u
INNER JOIN friendship f ON f.user1_email = u.email OR f.user2_email = u.email
GROUP BY u.email
HAVING COUNT(u.email) < 3
UNION
SELECT u.email, 0
FROM user u
WHERE u.email not in (SELECT user1_email from friendship)
AND u.email not in (SELECT user2_email from friendship);