T-SQL Compare 2 Usernames - tsql

I have 3 tables below. I am trying to compare the roles of each user. That is I want to show that John has 2 roles while Jane has 1. Currently I am only showing the common roles between them.
Sec_role (PK R_KEY, rname)
Role_User(PK R_KEY, PK USER_KEY)
User(PK USER_KEY, Name)
Sample data:
Sec_Role Role_User User
R_Key|rname| R_Key|User_Key User_KEY|NAME
1 |Analyst 1 |1 1 |John
2 |Sysadmin 2 |1 2 |Jane
2 |2
I am trying to get
User | Role | User2 | Role2
John | Analyst | Jane | (Empty because she isn't an analyst)
John | sysadmin| Jane |Sysadmin
SELECT U.Name AS User1,
U2.Name AS User2,
R.rname AS Role1
R2.Rname AS Role2
FROM Sec_Role AS R
LEFT OUTER JOIN Role_User AS RU
ON R.R_Key=RU.R_Key
LEFT OUTER JOIN User AS U
ON U.User_Key=RU.User_Key
LEFT OUTER JOIN Sec_Role AS R2
ON R2.R_Key=R.R_Key <---(I think this is the issue here)
LEFT OUTER JOIN Role_User AS RU2
ON R2.R_Key=RU2.R_Key
LEFT OUTER JOIN User AS U2
ON U2.User_Key=RU2.User_Key
WHERE U.Name ='John'
AND U2.Name='Jane'
Currently I am getting the Intersection of these two Users.
User | Role | User2 | Role2
John | sysadmin| Jane |Sysadmin

WHERE U.Name ='John'
OR U2.Name='Jane'
note an OR not an AND
why the 4 columns
SELECT U.Name AS User,
R.rname AS Role
FROM Sec_Role AS R
LEFT OUTER JOIN Role_User AS RU
ON R.R_Key=RU.R_Key
LEFT OUTER JOIN User AS U
ON U.User_Key=RU.User_Key
where uName in ('','')
order by 1, 2
Try this
SELECT U.Name AS User1,
U2.Name AS User2,
R.rname AS Role1
R2.Rname AS Role2
FROM Sec_Role AS R
LEFT OUTER JOIN Role_User AS RU
ON R.R_Key=RU.R_Key
LEFT OUTER JOIN User AS U
ON U.User_Key=RU.User_Key
and U.Name ='John'
LEFT OUTER JOIN Sec_Role AS R2
ON R2.R_Key=R.R_Key <---(I think this is the issue here)
LEFT OUTER JOIN Role_User AS RU2
ON R2.R_Key=RU2.R_Key
LEFT OUTER JOIN User AS U2
ON U2.User_Key=RU2.User_Key
AND U2.Name='Jane'

Related

Add Default Rows in Postgresql

I want to insert default rows into a result set if the LEFT JOIN is NULL.
For example if Jane has no roles, I want to return some default ones in the results.
A query like this will return the following:
SELECT * FROM employees LEFT OUTER JOIN roles ON roles.employee_id = employees.id
Employee ID | Employee Name | Role ID | Role Name
1 | John | 1 | Admin
1 | John | 2 | Standard
2 | Jane | NULL | NULL
I want to return:
Employee ID | Employee Name | Role ID | Role Name
1 | John | 1 | Admin
1 | John | 2 | Standard
2 | Jane | NULL | Admin
2 | Jane | NULL | Standard
Is there a good way to do this in PostgreSQL?
I think you're looking for
SELECT e.*, r.*
FROM employees e
JOIN roles r ON r.employee_id = e.id
UNION ALL
SELECT e.*, NULL, default_name
FROM employees e
JOIN (VALUES ('Admin'), ('Standard')) AS roles(default_name)
WHERE NOT EXISTS (
SELECT *
FROM roles r
WHERE r.employee_id = e.id
)
I don't think there's a (good) way around the UNION because a LEFT JOIN introduces only a single row per unmatched row. You might be able to lift out the join against the employees table though:
SELECT e.*, r.*
FROM employees e,
LATERAL (
SELECT r.id, r.name
FROM roles r
WHERE r.employee_id = e.id
UNION ALL
SELECT NULL, default_name
FROM (VALUES ('Admin'), ('Standard')) AS roles(default_name)
WHERE NOT EXISTS (
SELECT *
FROM roles r
WHERE r.employee_id = e.id
)
)

Using array_agg with multiple DISTINCT Columns

In this query, I'm listing all users in organization 123 but I also want a column showing which other teams they are on across all organizations.
My query right now will give me the team names but I'd also like to get the team id as well. The DISTINCT is necessary because they user may have different roles on the same team.
Bonus points if I can sort the teams by when the user was given a role, which currently gives an error as I have it now.
SELECT
users.*,
(
SELECT
to_json(array_agg(DISTINCT teams.name ORDER BY teams.name))
FROM roles r
INNER JOIN user_roles ur ON ur.role_id=r.id AND ur.user_id=users.id
INNER JOIN teams ON r.team_id=teams.id
-- ORDER BY r.created_at
) teams
FROM users
INNER JOIN user_roles ON users_roles.user_id=users.id
INNER JOIN roles ON roles.id = user_roles.role_id
WHERE roles.type = 'admin' AND roles.organization_id = 123
GROUP BY users.id
This returns:
name | teams
John Smith | ['Team 1', 'Team 2']
Jane Doe | ['Team 2', 'Team 3']
What I'd like to return is the team name with its primary key id:
name | teams
John Smith | {1: 'Team 1', 2: 'Team 2'}
Jane Doe | {2: 'Team 2', 3: 'Team 3'}
EDIT
Or better yet:
name | teams
John Smith | [{id: 1, name: 'Team 1'}, {id: 2, 'Team 2'}]
Jane Doe | [{id: 2, name: 'Team 2'}, {id: 3, 'Team 3'}]
Considering that your query is working fine, replace the following section of your query mentioned in question:
SELECT
to_json(array_agg(DISTINCT teams.name ORDER BY teams.name))
FROM roles r
INNER JOIN user_roles ur ON ur.role_id=r.id AND ur.user_id=users.id
INNER JOIN teams ON r.team_id=teams.id
-- ORDER BY r.created_at
with
(SELECT
json_object(array_agg(id::text order by created_at desc),
array_agg(name order by created_at desc)) from
( SELECT
DISTINCT on (teams.id) teams.id, teams.name , r.created_at
FROM roles r
INNER JOIN user_roles ur ON ur.role_id=r.id AND ur.user_id=users.id
INNER JOIN teams ON r.team_id=teams.id
ORDER BY r.created_at
)tab)
Here is how I ended up solving it, building off of #akhilesh answer.
SELECT
json_object(
array_agg(id :: text ORDER BY created_at DESC),
array_agg(name ORDER BY created_at DESC)
)
FROM
(
SELECT
*
FROM
(
SELECT
teams.id,
teams.name,
MAX(ur.created_at) created_at
FROM
roles r
INNER JOIN user_roles ur ON ur.role_id = r.id AND ur.user_id = users.id
INNER JOIN teams ON r.team_id=teams.id
GROUP BY
teams.id
) T
ORDER BY
T.created_at DESC
) teams

Transforming information in postgresql

So, I have 2 tables,
In the 1st table, there is an Information of users
user_id | name
1 | Albert
2 | Anthony
and in the other table, I have information
where some users have address information where it can either be home, office or both home and office
user_id| address_type | address
1 | home | a
1 | office | b
2 | home | c
and the final result I want is this
user_id | name | home_address | office_address
1 | Albert | a | b
2 | Anthony | c | null
I have tried using left join and json_agg but the information that way is not readable,
any suggestions on how I can do this?
You can use two outer joins, one for the office address and one for the home address.
select t1.user_id, t1.name,
ha.address as home_address,
oa.address as office_address
from table1 t1
left join table2 ha on ha.user_id = t1.user_id and ha.address_type = 'home'
left join table2 oa on oa.user_id = t1.user_id and ha.address_type = 'office';
A solution using JSON could look like this
select t1.user_id, t1.name,
a.addresses ->> 'home' as home_address,
a.addresses ->> 'office' as office_address
from table1 t1
left join (
select user_id, jsonb_object_agg(address_type, address) as addresses
from table2
group by user_id
) a on a.user_id = t1.user_id;
Which might be a bit more flexible, because you don't need to add a new join for each address type. The first query is likely to be faster if you need to retrieve a large number of rows.

Find Missing Values Between 3 Tables

I have 3 tables: Permissions, Roles, and RolePermissions. I would like to have a way to select Roles that are missing new rows in the Permissions table based on the RolePermissions table relationship to insert those values once new permissions are added.
I have had no luck finding how this can be done so that is why I'm asking here.
Table structure
Permissions | Roles | RolePermissions
------------------------------------------
Id | Id | Id
Name | Name | RoleId
| | PermissionId
Idea of sql but I know it's not correct:
-- Looking to be able to do something like
INSERT INTO RolePermissions (RoleId, PermissionId)
SELECT missingpermissions.PermissionId, missingpermissions.RoleId
FROM Permissions as p
INNER JOIN(
Select r.Id as RoleId, p.Id as PermissionId
FROM Role as r
LEFT JOIN RolePermissions as rp
ON r.Id = rp.RoleId
WHERE rp.PermissionId = p.Id
) as missingpermissions
ON p.id = missingpermissions.permissionid
Edited to format
You need to get your new permission and cross join all roles (to get all combinations of roles and new permissions).
INSERT INTO RolePermissions(RoleId, PermissionId)
SELECT r.ID AS RoleId,p.ID AS PermissionId
FROM Role r
CROSS JOIN (
--get all permissions currently not assigned to a role (presumably "new")
select p.*
from Permissions p
left join RolePermissions rp on p.id=rp.PermissionId
where rp.PermissionId is null
) p

Select rows of multiple tables via joins

I have some tables which are related to each others.
A short demonstration:
Sites:
id | clip_id | article_id | unit_id
--------------+------------+--------
1 | 123 | 12 | 7
Clips:
id | title | desc |
------------+--------
1 | foo2 | abc1
Articles:
id | title | desc | slug
------------+---------------------
1 | foo2 | abc1 | article.html
Units:
id | vertical_id | title |
------------------+-------+
1 | 123 | abc |
Verticals:
id | name |
-----------+
1 | vfoo |
Now I want to do something like below:
SELECT ALL VERTICAL, UNIT, SITE, CLIP, ARTICLE attributes
from VERTICAL, UNIT, SITE, CLIP, ARTICLE TABLES
WHERE vertical_id = 2
Can some one help me how can I use joins for this?
Here is a running example of possibly what you want: http://sqlfiddle.com/#!15/af63b/2
select * from
sites
inner join units on sites.unit_id=units.id
inner join clips on clips.id=sites.clip_id
inner join articles on articles.id=sites.article_id
inner join verticals on verticals.id=units.vertical_id
where units.vertical_id=123
The problem is, that the description you gave us did not clearly specify which columns to join:
(answered) Why does units have a link to site via site_id and sites a link back to units via unit_id?
(answered) Why does units have a link to verticals via vertical_id and verticals a link back to units via unit_id?
I am guessing that your data does not giva a consistent example to get rows using the join. For vertical_id=123 there is no corresponding entry in verticals.
Edit:
I corrected the SQL due to corrections within the question. With this the two questions are answered.
select s.id, s.clip_id, s.article_id, u.title, u.vertical_id, c.title, v.unit_id, c.desc, a.slug
from sites s
join units u on s.id = u.id
join clips c on u.id = c.id
join verticals v on c.id = v.id
join articles a on v.id = a.id
where v.vertical_id = 'any id'