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

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

Related

Updating the column that the table was joined on

I need to update a column that two tables were joined on and I'm having a difficult time wrapping my head around it. This is for SQL Server. Loose example below...
User
ID | Name | GroupID |
---------------------
1 | Bob | 100 |
2 | Alex | 300 |
3 | Sara | 300 |
Group
ID | Name |
----------------
100 | Produce |
200 | Cashier |
300 | Stocker |
GroupID is a foreign key to the Group table and they are being joined on that. I HAVE to update the GroupID column in User based on the Name column in Group. For example, I want Alex and Sara to change from 'Stocker' to 'Cashier'. My solution is below, but it doesn't seem to work.
UPDATE User
SET User.GroupID = G.ID
FROM User U
JOIN Group G ON U.GroupID = G.ID
WHERE User = 'Sara' OR User = 'Alex'
Expected Result
User
ID | Name | GroupID |
---------------------
1 | Bob | 100 |
2 | Alex | 200 |
3 | Sara | 200 |
You don't need and updated with join ..
but you could use a subquery for get the expected id from group
update user
set User.GroupID = (select id
from group where name = 'Cashier )
where User = 'Sara' OR User = 'Alex'

Postgres - how to get proper count with join

Sorry as a newcomer to sql (postgres in this case) I can't tease out the right answer from similar questions. So here's an example
I have two tables:
records:
id | status
----------------
1 | open
2 | open
3 | close
4 | open
events:
id | record_id | role | something_else
---------------------------------------------
1 | 2 | admin | stringA
2 | 1 | user | stringB
3 | 4 | admin | stringC
4 | 2 | admin | stringD
5 | 2 | admin | stringE
6 | 2 | user | stringF
7 | 3 | user | stringG
I basically would like to have a count(status) that reflects how many records have at least one events.role = 'admin' in the events table
in the above example it would be:
status | count
---------------
open | 2
close | 0
Any help much appreciated!
No need for nested queries. You can just use conditional aggregation:
select r.status, count(distinct r.id) filter(where e.role = 'admin') cnt
from records r
inner join events e on e.record_id = r.id
group by r.status
Demo on DB Fiddle:
status | cnt
:----- | --:
close | 0
open | 2
I basically would like to have a count(status) that reflects how many records have at least one events.role = 'admin' in the events table.
I would suggest:
select r.status, count(*) filter (where has_admin)
from (select r.*,
(exists (select 1 from events e where e.record_id = r.id and e.role = 'admin')) as has_admin
from records r
) r
group by r.status;
For your small data sample, the difference between exists and a join doesn't matter. With more data, though, the exists does not multiply the number of rows, which should make it a bit faster. Also, this guarantees that all statuses are included, even those with no events.

Postgres update column, on conflict ignore this row

I have a table with email and secondary_email. email column has a unique constraint, while secondary_email can be repeated across rows.
I have to write a query to copy secondary_email to email. If there is a conflict, then ignore this row.
This query
UPDATE users SET email = secondary_email
WHERE NOT EXISTS
(SELECT 1 FROM users WHERE email=secondary_email)
still throws the error ERROR: duplicate key value violates unique constraint "users_email_key"
Users Before
+----+-------+-----------------+
| id | email | secondary_email |
+----+-------+-----------------+
| 1 | NULL | NULL |
| 2 | NULL | NULL |
| 3 | NULL | |
| 4 | NULL | e1#example.com |
| 5 | NULL | e1#example.com |
| 6 | NULL | e2#example.com |
+----+-------+-----------------+
Users After
+----+----------------+-----------------+
| id | email | secondary_email |
+----+----------------+-----------------+
| 1 | NULL | NULL |
| 2 | NULL | NULL |
| 3 | NULL | |
| 4 | e1#example.com | e1#example.com |
| 5 | NULL | e1#example.com |
| 6 | e2#example.com | e2#example.com |
+----+----------------+-----------------+
You need table aliases to fix your query:
UPDATE users u
SET email = u.secondary_email
WHERE NOT EXISTS (SELECT 1 FROM users u2 WHERE u2.email = u.secondary_email);
For your overall problem, check for no duplicates within the column as well:
UPDATE users u
SET email = u.secondary_email
FROM (SELECT secondary_email, COUNT(*) as cnt
FROM users u
GROUP BY secondary_email
HAVING COUNT(*) = 1
) s
WHERE s.secondary_email = u.secondary_email AND
NOT EXISTS (SELECT 1 FROM users u2 WHERE u2.email = u.secondary_email);
Or choose the first one:
UPDATE users u
SET email = u.secondary_email
FROM (SELECT u.*,
ROW_NUMBER() OVER (PARTITION BY secondary_email ORDER BY user_id) as seqnum
FROM users u
) s
WHERE s.user_id = u.user_id AND
s.seqnum = 1 AND
NOT EXISTS (SELECT 1 FROM users u2 WHERE u2.email = u.secondary_email);
Note: This will also filter out NULL values which seems like a good idea.
Here is a db<>fiddle.

sql count base table with subtable condition

user_table
| uid |
----------
| 1 |
| 2 |
| 3 |
| 4 |
user_role_table
| uid | role |
-----------------------------
| 1 | Main1Role |
| 1 | Main2Role |
| 1 | Sub1Role |
| 1 | Sub2Role |
| 2 | Main1Role |
| 2 | Sub1Role |
| 3 | Main1Role |
| 3 | Main2Role |
| 4 | Sub1Role |
| 4 | Sub2Role |
if the user has a main role he should not be counted for subrole.
uid 1 is counted in Main
uid 2 is counted in Main
uid 3 is counter in Main
uid 4 is counted in sub
it is like sum of users with higher priority given to main user
Expected Output
MainRoleCount: 3
SubRoleCount: 1
I am not sure about your expected output.
I understood: You want to get the number of uids of every role. But if one uid is in both, a main and a sub role, the count has to ignore the uid for the total of the sub roles.
So, in your example the counts are as follows:
Main1Role is for uids 1,2,3: Count = 3
Main2Role is for uids 1,3: Count = 2
Sub1Role is for uids 1,2,4, but 1 and 2 have a main role, so it is only for 4: Count = 1
Sub2Role is for 1,4, but 1 has a main role, so it is only for 4: Count = 1
Assuming this is what you want:
demo:db<>fiddle
SELECT
role,
SUM (
CASE WHEN role IN ('Main1Role', 'Main2Role') THEN 1
ELSE CASE WHEN ARRAY['Main1Role', 'Main2Role'] && array_agg THEN 0
ELSE 1 END
END
)
FROM (
SELECT
*,
array_agg(role) OVER (PARTITION BY uid)
FROM
user_role_table
) s
GROUP BY role
For added expected output. Same idea, but subquerying the role types:
demo:db<>fiddle
You can try this simple query to get your desired output-
SELECT
CASE
WHEN role_name = 'M' THEN 'MainRoleCount'
WHEN role_name = 'S' THEN 'SubRoleCount'
END role_name,
COUNT(*)
FROM
(
SELECT uid,MIN(LEFT(role,1)) role_name
FROM your_table
GROUP BY uid
)A
GROUP BY role_name
Output will be-
role_name Count
Main1Role 3
Sub1Role 1

Include admin role in users table from roles table

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.