Postgresql: How to relate data between two tables? - postgresql

I've searched this question for Postgresql 9.3 and seen no solutions with adequate answers. The Psql documentation also wasn't helpful.
Say I have two tables:
users (username, password, id)
data (title, description, id)
I've created a foreign key, but I'm not exactly clear on how that works. I want users to be able to see and edit their data and I want the data to be related to the user. It's the kind of thing we see all the time on blogs or facebook or twitter.
How do I create a relationship between the tables so that each piece of data a user submits is associated with them?

Assuming id on both tables is the id of the user, what you need to get all users data is:
SELECT u.username, d.title, d.description
FROM users u
LEFT JOIN data d ON d.id = u.id
If there is no data, d.titlea and d.description will be NULL. If you only want users with data just replace the LEFT JOIN with a JOIN
EDIT: (based on comments)
If you want users to have ownership of the data and if the data only belongs to an user at a time you just need to have the user_id in the data table. I was assuming the id in table was of the user. In that case when creating the table you need to do something like:
CREATE TABLE data (
id SERIAL,
title varchar(50) NOT NULL,
description varchar(255),
user_id references users(id) -- this is your foreign key
);
and then to query is once again with user_id adjustements:
SELECT u.username, d.title, d.description
FROM users u
LEFT JOIN data d ON d.user_id = u.id

Related

PostgreSQL: proper way to delete 'orphaned' records

In my current project, a have a few cases where, within data pump operations, I have to execute queries like this (it's not a real example, but it should give you some idea):
DELETE FROM notification
WHERE user_id NOT IN (SELECT id FROM user)
On big tables, such construction performs poorly, I believe it's because of NOT IN construction, which makes it impossible to use indexes.
Such approach should perform better:
DELETE FROM notification
USING (
SELECT n.user_id, u.id FROM notification
LEFT OUTER JOIN user u ON n.user_id = u.id
) i
WHERE
notification.user_id = i.user_id
AND i.id IS NULL
... but it looks a bit overcomplicated.
Is there a better way / best practice for such operations?
We can use EXISTS instead:
DELETE FROM notification
WHERE NOT EXISTS
(SELECT id FROM user WHERE id = notification.user_id)
I'd use NOT EXISTS.
DELETE FROM notification n
WHERE NOT EXISTS (SELECT *
FROM "user" u
WHERE u.id = n.user_id);
But what you really should do is adding a foreign key constraint so that such rows cannot exist in the first place. Assuming that "user".id already is a (primary) key:
ALTER TABLE notification
ADD FOREIGN KEY (user_id)
REFERENCES "user"
(id);
If "user".id isn't a (primary) key, you first need to change that and make it a (primary) key.

Roles permissions and user roles, proper mapping?

I have a basic role based access control. Single table for permission (name, description), single table for role (name, description). Question is, how to make proper mapping with the users? So in my mind comes 1 solution:
role_permission_set - table
(role_permission_id PK, role_id (many-to-one), permission_id (many-to-one))
user_role - table
(user_role_id PK, user_id (many-to-one), role_permission_id (many-to-one))
Two tables for mapping roles permissions and users roles. Is there a better way of doing this?
You have this right.
Break it down into two independent many-to-many relationships. One is between user and role. The other is between role and permission.
Each of those two many-to-many relationships will need a join table similar to what you described.
The only mistake I see in your post is that user_role should contain the role_id instead of the role_permission_id.
create table user_role (
user_role_id int serial primary key,
user_id int not null references user(user_id),
role_id int not null references role(role_id),
constraint uq_user_role unique(user_id, role_id)
);
When you want to get a list of permission values for a user:
select distinct p.name
from user_role ur
join role_permission rp on rp.role_id = ur.role_id
join permission p on p.permission_id = rp.permission_id
where ur.user_id = 123;

How would I associate users with different company tables?

In the below diagram you can see I have profiles for users. A user means the profile is associated with a login. If they don't have a login account they are merely a "contact" at the company tables you see on the right.
I am creating a CRM that needs to be able to store many facilities, customers, vendors, etc.
My question is how would I associate that profile with the company it belongs too? If I went to there profile page, for example, I should see what company they work for, regardless of what table the company is in.
I have 2 ideas but both seem flawed. One is to make a separate profile table for contacts for each. Example. vendor_profiles, customer_profiles, etc. This seems messy.
My other idea was to make a vendor_id, customer_id, etc column in the profile table, set them all to NULL by default, and if they belong to a vendor then my application code would check each for a number, and I think you get where I am going from there. Again, seems very messy, and inefficient.
I also considered grouping the Vendors, Customers, so on into one table and just setting a "type" but they all store very different info and are used in different, and often, relational ways.
Introduce a new organization table which unifies those aspects of vendor, customer,
facility and our_company that they all share in common:
CREATE TABLE organization (
organization_id serial4
type text);
CREATE TABLE profile (
profile_id serial4
user_id int4 REFERENCES user
organization_id int4 REFERENCES organization
...);
Then add an organization_id foreign key to vendor, customer, facility and our_company
so these tables can be joined to organization when the extra info in those tables are needed.
CREATE TABLE vendor (
vendor_id serial4
organization_id int4 REFERENCES organization
vendor_specific_field text
...);
Note that if you use "unique" names for id fields (such as organization_id instead of id) which have a consistent meaning across all tables, then you can JOIN ... USING syntax
SELECT *
FROM profile p
INNER JOIN organization o USING (organization_id)
INNER JOIN vendor v USING (organization_id)
WHERE o.type = 'vendor'
instead of
SELECT *
FROM profile p
INNER JOIN organization o ON p.organization_id = o.id
INNER JOIN vendor v ON v.organization_id = o.id
WHERE o.type = 'vendor'
which may improve readability and reduce potential errors in join conditions. (You'll never have to wonder what field in one table connects to the id field in another table.)

removing data which are not refenceing to other table POSTGRES

I have one table USER and some other tables like USER_DETAILS ,USER_QUALIFICATION etc USER_ID references to all such table i want to remove those USER_ID which are not present in any other tables.
Deleting all of the users that are not present in a connected table:
DELETE FROM table WHERE user_id NOT IN (SELECT user_id FROM other_table)
If you want to delete only users that are not found in any table than you can add
AND NOT IN (SELECT user_id FROM another_table)
Alternatively you can create a tmp table and merge in all the user_ids that you want to keep and use that table in the sub-select for the NOT IN.
Use a DELETE with a not exists condition for all related tables:
delete from "USER" u
where not exists (select *
from user_details ud
where ud.user_id = u.user_id)
and not exists (select *
from user_qualification uq
where uq.user_id = u.user_id);
Note that user is a reserved word, and thus needs to be quoted to be usable as a table name. But quoting makes it case-sensitive. So "USER" and "user" are two different table names. As you have not included the DDL for your tables I cannot tell if your table is named "USER" or "user".
In general I would strongly recommend to avoid using double quotes for identifies completely.

Is it possible to insert values in column using just a primary key reference?

I have a table 'users' with the columns:
user_id(PK), user_firstname, user_lastname
and another table 'room' with the columns:
event_id(PK), user_id(FK), user_firstname, user_lastname....(and more columns).
I want to know if it is possible to fill the user_firstname and user_lastname automatically just knowing the user_id column.
Like the default value of user_firstname would be like: "select users.user_firstname where users.user_id = user_id"
I don't know if was clear enough...As you can see my knowledge in database is very narrow.
What you want to achieve can be done with JOINs. They will avoid those redundant user_firstname and user_lastname columns. So you'd just fetch from both tables when querying the room table and you get the extra columns of users into the result set:
SELECT * FROM room AS r INNER JOIN users AS u ON r.user_id = u.user_id;
The thing we did here is called normalization. Another important thing to take care of are foreign key constraints and their cascades, in your case room.user_id references user.user_id. A delete on user should most probably cascade to room, if you want to delete users, instead of flagging them deleted.
The columns user_firstname and user_lastname do not belong in your room table. The user_id column references the users table, that is all you need.
To select the data, you can use a JOIN statement, something like
SELECT R.event_id, R.user_id, U.user_firstname, U.user_lastname
FROM room AS R
JOIN users AS U ON R.user_uid = U.user_id
The answer here is sideways to the question. You do not want a user_firstname and user_lastname column in the Event table. The user_id is a proxy for that row of the entire User table. When you need to access user_firstname, you do a JOIN of the two tables on the common column.