Find Missing Values Between 3 Tables - tsql

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

Related

Postgresql, inner join or subquery or view?

I have the following tables:
user
car
dealer
user_metrics
- user_id (FK) (required)
- dealer_id (FK) (can be null)
- car_id (FK) (can be null)
- saved
- .... other columns
A user can save a car or a dealership, when that happens the user_metrics.saved is set to true and the related car_id or dealership_id is set (car_id and dealership_id are exclusive, only one is set for a row).
I want user A to be able to see all users that have saved the same cars / dealerships.
So, if user A has saved car 1, 2,3 and dealership 5,7, I want to get all users that have saved any of those cars / dealerships.
I thought about inner join on user_metrics, but, I am not sure how to write the entire query that would deliver on this.
What query would allow me to get all users that have saved any of the cars/dealerships a certain user has saved?
If I understand as well maybe the below query solve your problem.
First should find a list of user A has been reserved after that should search which of car or dealer used by another user
with user_saved_data as (
select um.*,
u.name,
...
from user_metrics um
inner join user u
on um.user_id = u.id
where um.saved = true
and u.id = $1 -- User id of user 'A' or any username (Or use other column for create custom condition)
)
select usd.name as current_reserved_user,
u.name as reserved_by_user,
d.*,
c.*
from user_metrics um
inner join user u on um.user_id = u.id
left join user_saved_data usd on usd.dealer_id notnull and usd.dealer_id = um.dealer_id
left join user_saved_data usd on usd.car_id notnull and usd.car_id = um.car_id
left join dealer d on um.dealer_id = d.id
left join car c on um.car_id = c.id

Join two postgresql queries

I have the following query
SELECT role_uuid FROM users WHERE email = 'email#domain.com'
I also have a roles table the following fields:
uuid
name
created_at
I'm hoping to have 1 query that gives lets me select the role by email and get the name and created_at field from the roles table.
I've tried things like this but I can't quite figure it out.
SELECT *
FROM ( SELECT * FROM users WHERE email = 'email#domain.com') AS A
JOIN ( SELECT * FROM roles WHERE uuid = A.role_uuid) AS B
WHERE A.role_uuid = B.uuid
You JOIN the two tables which gives you a table with all the fields from both source tables. Then you use WHERE to filter and SELECT to specify the fields that you want to be returned.
SELECT r.name, r.created_at
FROM users u JOIN roles r ON (u.role_uuid = r.uuid)
WHERE u.email = 'email#domain.com'
If you run into naming conflicts because of fields from both tables sharing the same name you can use AS to define fieldnames for the output columns:
SELECT r.name AS rolename, u.name AS username, r.created_at
FROM users u JOIN roles r ON (u.role_uuid = r.uuid)
WHERE u.email = 'email#domain.com'

LIKE search of joined and concatenated records is really slow (PostgreSQL)

I'm returning a unique list of id's from the users table, where specific columns in a related table (positions) contain a matching string.
The related table may have multiple records for each user record.
The query is taking a really really long time (its not scaleable), so I'm wondering if I'm structuring the query wrong in some fundamental way?
Users Table:
id | name
-----------
1 | frank
2 | kim
3 | jane
Positions Table:
id | user_id | title | company | description
--------------------------------------------------
1 | 1 | manager | apple | 'Managed a team of...'
2 | 1 | assistant | apple | 'Assisted the...'
3 | 2 | developer | huawei | 'Build a feature that...'
For example: I want to return the user's id if a related positions record contains "apple" in either the title, company or description columns.
Query:
select
distinct on (users.id) users.id,
users.name,
...
from users
where (
select
string_agg(distinct users.description, ', ') ||
string_agg(distinct users.title, ', ') ||
string_agg(distinct users.company, ', ')
from positions
where positions.users_id::int = users.id
group by positions.users_id::int) like '%apple%'
UPDATE
I like the idea of moving this into a join clause. But what I'm looking to do is filter users conditional on below. And I'm not sure how to do both in a join.
1) finding the keyword in title, company, description
or
2) finding the keyword with full-text search in an associated string version of a document in another table.
select
to_tsvector(string_agg(distinct documents.content, ', '))
from documents
where users.id = documents.user_id
group by documents.user_id) ## to_tsquery('apple')
So I was originally thinking it might look like,
select
distinct on (users.id) users.id,
users.name,
...
from users
where (
(select
string_agg(distinct users.description, ', ') ||
string_agg(distinct users.title, ', ') ||
string_agg(distinct users.company, ', ')
from positions
where positions.users_id::int = users.id
group by positions.users_id::int) like '%apple%')
or
(select
to_tsvector(string_agg(distinct documents.content, ', '))
from documents
where users.id = documents.user_id
group by documents.user_id) ## to_tsquery('apple'))
But then it was really slow - I can confirm the slowness is from the first condition, not the full-text search.
Might not be the best solution, but a quick option is:
SELECT DISTINCT ON ( u.id ) u.id,
u.name
FROM users u
JOIN positions p ON (
p.user_id = u.id
AND ( description || title || company )
LIKE '%apple%'
);
Basically got rid of the subquery, unnecessary string_agg usage, grouping on position table etc.
What it does is doing conditional join and removing duplicate is covered by distinct on.
PS! I used table aliases u and p to shorten the example
EDIT: adding also WHERE example as requested
SELECT DISTINCT ON ( u.id ) u.id,
u.name
FROM users u
JOIN positions p ON ( p.user_id = u.id )
WHERE ( p.description || p.title || p.company ) LIKE '%apple%'
OR ...your other conditions...;
EDIT2: new details revealed setting new requirements of the original question. So adding new example for updated ask:
Since you doing lookups to 2 different tables (positions and uploads) with OR condition then simple JOIN wouldn't work.
But both lookups are verification type lookups - only looking does %apple% exists, then you do not need to aggregate and group by and convert the data.
Using EXISTS that returns TRUE for first match found is what you seem to need anyway. So removing all unnecessary part and using with LIMIT 1 to return positive value if first match found and NULL if not (latter will make EXISTS to become FALSE) will give you same result.
So here is how you could solve it:
SELECT DISTINCT ON ( u.id ) u.id,
u.name
FROM users u
WHERE EXISTS (
SELECT 1
FROM positions p
WHERE p.users_id = u.id::int
AND ( description || title || company ) LIKE '%apple%'
LIMIT 1
)
OR EXISTS (
SELECT 1
FROM uploads up
WHERE up.user_id = u.id::int -- you had here reference to table 'document', but it doesn't exists in your example query, so I just added relation to 'upoads' table as you have in FROM, assuming 'content' column exists there
AND up.content LIKE '%apple%'
LIMIT 1
);
NB! in your example queries have references to tables/aliases like documents which doesn't reflect anywhere in the FROM part. So either you have cut in your example real query with wrong naming or you have made other way typo is something you need to verify and adjust my example query accordingly.

How to enumerate all enabled roles for all users in PostgreSQL?

If you want to check the roles that a user has access to, there is no easy way in PostgreSQL. In the information_schema there are relations enabled_roles and applicable roles but these only provide the privileges of the current_user. So how can I access the same information for any user?
The trick is to make a recursive query over the system catalog relations pg_roles and pg_auth_members:
WITH RECURSIVE membership_tree(grpid, userid) AS (
-- Get all roles and list them as their own group as well
SELECT pg_roles.oid, pg_roles.oid
FROM pg_roles
UNION ALL
-- Now add all group membership
SELECT m_1.roleid, t_1.userid
FROM pg_auth_members m_1, membership_tree t_1
WHERE m_1.member = t_1.grpid
)
SELECT DISTINCT t.userid, r.rolname AS usrname, t.grpid, m.rolname AS grpname
FROM membership_tree t, pg_roles r, pg_roles m
WHERE t.grpid = m.oid AND t.userid = r.oid
ORDER BY r.rolname, m.rolname;
This gives a view of all users in the system with all inherited role memberships. Wrap this in a view to have this utility always handy.
Cheers,
Patrick
This was very helpful as I was looking for just this sort of information. Adapting the work above to include a level to keep track of the inheritance
WITH RECURSIVE membership_tree(grpid, userid, lvl) AS (
-- Get all roles and list them as their own group as well
SELECT
pg_roles.oid
, pg_roles.oid
, 0
FROM
pg_roles
UNION ALL
-- Now add all group membership
SELECT
m_1.roleid
, t_1.userid
, lvl + 1
FROM
pg_auth_members m_1
INNER JOIN
membership_tree t_1
ON
m_1.member = t_1.grpid
)
SELECT DISTINCT
t.userid
, r.rolname AS usrname
, t.grpid
, m.rolname AS grpname
, t.lvl
FROM
membership_tree t
INNER JOIN
pg_roles r
ON
t.userid = r.oid
INNER JOIN
pg_roles m
ON
t.grpid = m.oid
ORDER BY
r.rolname
, t.lvl
, m.rolname;

How can I feed back a subquery as a boolean column in PostgreSQL?

We store our accounts information in a PostgreSQL database.
Accounts are in the "accounts" table, groups in the "grp" table, and they're tied together by the "account_grp" table, which maps account_id to grp_id.
I'm trying to craft a query which will give me a view which lets me search for whether members of one group are members of another group, i.e. I want an "is_in_foobar_group" column in the view, so I can SELECT * FROM my_view WHERE grp_id = 1234; and get back
username | is_in_foobar_group | grp_id
---------+--------------------+-------
bob | true | 1234
alice | false | 1234
The foobar bit is hardcoded, and will not need to change.
Any suggestions?
Simpler, faster, more convenient:
WITH x AS (SELECT 1234 AS foobar) -- optional, to enter value only once
SELECT a.username
,EXISTS (
SELECT 1 FROM account_grp g
WHERE g.account_id = a.account_id
AND g.grp_id = x.foobar
) AS is_in_foobar_group
,x.foobar AS grp_id
FROM accounts a, x
Maybe using the EXISTS operator would help:
http://www.postgresql.org/docs/9.2/static/functions-subquery.html#FUNCTIONS-SUBQUERY-EXISTS
I'm not sure you can use it in a SELECT statement, and I don't have a PostgreSQL instance to check it.
Worst case you'll have to do 2 queries, something like:
SELECT username, true, grp_id
FROM accounts a INNER JOIN account_grp g1 on a.account_id = g.account_id
WHERE EXIST (SELECT 1 FROM account_grp g2
WHERE g2.account_id = a.account_id and g2.grp_id = [foobar])
UNION
SELECT username, false, grp_id
FROM accounts a INNER JOIN account_grp g1 on a.account_id = g.account_id
WHERE NOT EXIST (SELECT 1 FROM account_grp g2
WHERE g2.account_id = a.account_id and g2.grp_id = [foobar])