Restrict what a user can see in an SQL query - sql

this is a bit out there, but I was wondering if it is possible to restrict a usergroup in SQL to only see certain subsets of data..
Say for example a table of products, ProductName and State are the two columns. I only want a usergroup to see products from their state, when they do a query like SELECT * FROM Products
Is that possible?

Restrict access to the underlying table.
And give your user groups access to views on these underlying tables.
So:
Given table:
CREATE TABLE EVERYTHING ID INTEGER, TYPE CHAR(1), SEMISECRET CHAR(20), REALLYSECRECT CHAR(20));
You can create views which give access to only certain columns:-
CREATE VIEW SOMESTUFF AS SELECT ID, TYPE, SEMISECRET FROM EVERTHINK;
You can create views which give access to certain rows:-
CREATE VIEW TYPESLICE AS SELECT ID, TYPE, REALLYSECRECT WHERE TYPE ='X';
Much more is possible, but that's the basics.

Create one or more views corresponding to your subsets and set the appropriate permissions on them.

Related

Is it possible to inherit data from another table across schemas?

I'm trying to create a custom list of houses that a user can plot on a map. Each user can create houses, or edit the houses they've created. However there is a common set of houses that all users must have, and that can not be edited or deleted.
The users are divided into one schema per user, with a user.houses table listing all the custom houses for a single user.
Similarly, the public.houses table holds the houses available to every user. The structure of these tables are identical.
Is there a way to not duplicate the public.houses table data across all the user.houses tables?
I was able to use inheritance to ensure that the user.houses table exactly matches the public.houses table structure. Now I want to do this with the data as well.
INSERT INTO public.houses id VALUES (1), (2) ;
INSERT INTO user_a.houses id VALUES (3) ;
INSERT INTO user_b.houses id VALUES (4) ;
SELECT id FROM user_b.houses ;
I expect the output to be ids: [1, 2, 4]
This is too long for a comment.
You may have already made a bad decision by having one schema per user. There are some valid reasons for doing this -- security would be the number one reason. If you had a strong requirement that the data be separated. Another reason would be if the users were actually using different database versions (their own installations).
That said, it is usually better to support multiple users within a single databases/schema. The tables would identify the user for each entity (where it is important). Some tables, such as reference tables would be shared without ownership.
One thing that you can do is create views. The view:
create view v_user_houses as
select p.*
from public.houses p
union all
select u.*
from user.houses u;
You would need a separate view for each user.
An alternative would be to copy the publish houses into all the users table. When anything changes in the pubic houses, then you would propagate those changes to the users houses tables.
Try this
SELECT array_agg(a.id) FROM (
select id from public.houses
union all
select id from user_a.houses
union all
select id from user_b.houses ) AS a

How do I create a table the same time as other tables that pulls totals from each?

For class I'm creating a database that keeps track of my finances. I have a table for each type of item I purchase. For example Rent, Food, Hygiene, Entertainment,etc... I also have a table called Register that I want to display the monthly total for each item. The column names for the totals in the tables are as follows: MonthlyRentTotal, MonthlyFoodTotal, etc...
I want the Register table to pull the total from each Table so I don't have to enter them twice. Any Ideas on how I can do that? I don't want to create a view either. I want it to be an actual table. I'm not even sure if this is possible.
I assume that Rent, Food, Hygiene, Entertainment have same column type.
INSERT INTO Register
SELECT *
FROM
(SELECT SUM(a.rent_value) AS value,
'monthlyrent' AS TYPE
FROM Rent a
UNION SELECT SUM(b.food_value) AS value,
'monthlyfood' AS TYPE
FROM Food b) d pivot(max(value)
FOR TYPE IN (monthlyrent, monthlyfood)) piv;
Data was pivoted in order to be inserted into Register table.
You can put this query in a stored procedure or simply run it manually. If you want to have updated data in the Register table I suggest to :
Create a stored procedure using a TRUNCATE for Register table followed by the above query
Create an SQL Job and schedule the run of the stored procedure anytime you need.
Hope this helps. Let me know if you need additional details.
You should only separate the items into separate tables if there is a compelling reason. For the items you describe, I see no compelling reason.
I would imagine a data structure like this:
itemCategories -- contains the list of categories you care about, such as "food", "hygiene", and so on.
Purchases -- contains each purchase, with columns like purchaseDate, location, itemCategory, description, and so on.
You may want additional tables for other entities, such as "location".
What you are calling a Register table would then simply be a query or view on Purchases.

in postgres, is it possible to optimize a VIEW of UNIONs

in the database there are many identical schemas, cmp01..cmpa0
each schema has a users table
each schema's users table's primary key has its own unique range
for example, in cmp01.users the usr_id is between 0x01000000 and 0x01ffffffff.
is there any way I could define a VIEW global.users that is a union of each of the cmp*.union tables in such a way that, if querying by usr_id, the optimizer would head for the correct schema?
was thinking something like:
create view global.users as
select * from cmp01.users where usr_id between 0x01000000 and 0x01ffffffff
union all
select * from cmp02.users where usr_id between 0x02000000 and 0x02ffffffff
....
would this work? NO. EXPLAIN ANALYZE shows all schema used.
Is there an approach that might give good hints to the optimizer?
Why not create a table in a public schema that has all users in it, possibly with an extra column to store the source schema. Since the ids are globally unique, you could keep the id column unique:
create table all_users (
source_schema varchar(32),
usr_id int primary key,
-- other columns as per existing table(s)
);
Poluate the table by inserting all rows:
insert into all_users
select 'cmp01', * from cmp01.users union
select 'cmp02', * from cmp02.users union ...; -- etc
Use triggers to keep the table up to date.
It's not that hard to set up, and it will perform every well
What about creating a partitioned table? The master table would be created as global.users and it would be partitioned by the schema name.
That way you'd get the small user tables in each schema (including fast retrievals) provided you can create queries that PostgreSQL can optimize i.e. including the schema name in the where condition. You could also create a view in each schema that would hide the needed schema name to query the partitioned tables. I don't think it would work by specifying only the user_id. I fear that PostgreSQL's partitioning features are not smart enough for that.
Or use just one single table, and create views in each schema with an instead of trigger and limiting the result to that schema's users.
Try something like:
create view global.users as
select *
from (select 'cmp01' sel_schema, 0x01000000 usr_id_start, 0x01ffffffff usr_id_end
union all
select 'cmp02' sel_schema, 0x02000000 usr_id_start, 0x02ffffffff usr_id_end) s
join (select u1.*, 'cmp01' schema from cmp01.users u1
union all
select u2.*, 'cmp02' schema from cmp02.users u2) u
on s.sel_schema = u.schema
and include a condition like specified_usr_id between usr_id_start and usr_id_end when querying the view by a specified user ID.

Grant access to subset of table to user on PostgreSQL

I know that I can use views to grant access to a subset of attributes in a table. But how can I grant access to particular tuples only? Say I have a table of registered students, a username attribute and then some other like degree_status, how do I grant access so that user A can only select from the table a tuple corresponding to username A ? I have a database exam and I'm studying some past papers and I came across this question but I don't know how to answer it and I cant find how to do it from my book "Dtabase System: A practical Approach to Database Design, Implementation and Management'
Thanks any help is much appreciated!
Matt
Say that you got :
Table items (item_id, ...)
Table users (user_id, ...)
Table users_permissions( user_id, item_id, perm_type )
You could create a VIEW like this :
SELECT i.*, p.perm_type
FROM items JOIN users_permissions USING (item_id)
WHERE user_id = get_current_user_id();
Users can select from this view but not remove the WHERE and JOIN restricting the permissions.
The get_current_user_id() function is likely to be the major problem ;)
Along the lines of peufeu's answer, in Postgresql the current user name is available through the function current_user. So a view
CREATE VIEW available_bigtable AS
SELECT * FROM bigtable
WHERE username = current_user;
looks like it does what you need. Grant SELECT to everyone on the view, but to no one (except admins) on the underlying bigtable.
The Veil project provides a framework for row-level access control in PostgreSQL.
How about creating a function that takes the user id and returns the subset of rows he has access to?
CREATE FUNCTION user_items(integer) RETURNS SETOF items AS $$
SELECT * FROM items WHERE user_id = $1
$$ LANGUAGE SQL;
SELECT * FROM user_items(55); # 55 being the user id
edit Thinking about it more, this could cause quite a performance hit, as the user_id condition would be applied to the whole data set, prior to any other "user-land" conditions.
For example, SELECT * FROM user_items(55) WHERE id=45 would first filter the entire table for user items, and only than find the ID on that subset.
With views, the query planner can decide on the optimal order to evaluate the conditions (where he'll probably filter for the ID first, than for user id). When using a function like I suggested, postgres can't do that.

Join performance

My situation is:
Table member
id
firstname
lastname
company
address data ( 5 fields )
contact data ( 2 fields )
etc
Table member_profile
member_id
html ( something like <h2>firstname lastname</h2><h3>Company</h3><span>date_registration</span> )
date_activity
chat_status
Table news
id
member_id (fk to member_id in member_profile)
title
...
The idea is that the full profile of the member, when viewed is fetched from the member database, in for instance a news overview, the smaller table which holds the basis display info for a member is joined.
However, i have found the need for more often use for the member info that is not stored in the member_profile table, e.g. firstname, lastname and gender, are nescesary when someone has posted a news item (firstname has posted news titled title.
What would be better to do? Move the fields from the member_profile table to the member table, or move the member fields to the member_profile table and perhaps remove them from the member table? Keep in mind that the member_profile table is joined a lot, and also updated on each login, status update etc.
You have two tables named member so i have the feeling your question isn't formed correctly.
What is the relationship between these tables? It looks like you have 3 tables, all one-to-one. So all you need to do is change (fk to member_id in member_profile) to (fk to id in member).
Now you can join in data from either of the 2 extra tables as you wish, without always having to go through member_profile.
[Edit] Also I assume that member_profile.member_id is a fk to member.id. If not, I believe it should :)
Combine them into one table so you're normalizing the name data then create 2 views which replicate the original two tables would be the easy option
Separating the tables between mostly-static fields and frequently-updated fields will improve write performance. So I would stay with what you're doing. If you cache the information from both tables together in a member object, read performance (and thus joining) is less of an issue.