How would I associate users with different company tables? - postgresql

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.)

Related

Postgres relations for owner and users

I'm doing an exercise to learn database designs. This is some kind of fantasy soccer teams. I have this initial tables, users and teams. One team can have many users and a user can join and belongs to one team.
USERS
PK id
FK team_id -> TEAMS.id
username
TEAMS
PK id
name
But I also want to allow users to create their own teams if they don't want to join another team. What if I added owner_id in teams table?
TEAMS
PK id
FK owner_id -> USERS.id
name
I feel like this is not the right way to do. What is the best way to do this?
You could have a flag in users that indicates if the user owns the team they're a member of and a partial unique index to restrict this flag only being set once for a team.
CREATE TABLE teams
(id serial,
owner_id integer,
PRIMARY KEY (id));
CREATE TABLE users
(id serial,
team_id integer
NOT NULL,
is_owner boolean
NOT NULL
DEFAULT FALSE,
PRIMARY KEY (id),
FOREIGN KEY (team_id)
REFERENCES teams
(id));
CREATE UNIQUE INDEX users_team_id_is_owner
ON users
(team_id)
WHERE is_owner;
db<>fiddle
The index only includes the row that satisfy the WHERE clause, thus all of them have the flag is_owner set to true. Within that subset of rows the team_id must be unique which follows that there can not be any two rows with the same team_id and is_owner set to true.
To ensure a user must be a member of a team, set the team_id to be NOT NULL.
There can be teams without owners in that schema though. But as I understood your question, that is OK.

PostgreSQL hierarchy(?) structure

Please excuse my ignorance. I'm certain this is a FAQ, but I don't know the terminology well enough to know what to look for.
My company uses the following structure in terms of territory (example following):
Customer -> Market -> Area -> District -> Region
XYZ Co. -> Queens -> NYC -> Mid Atlantic -> Northeast
Each customer has only one market. Each market has only one district, and so forth. (I'm not sure if you'd call that one-to-many or many-to-one. I don't want to label it incorrectly).
This is how I have things set up right now:
create table region(
id int not null primary key,
name varchar(24)
);
create table district(
id int not null primary key,
name varchar(24),
region_id int references region(id) on update cascade
);
create table area(
id int not null primary key,
name varchar(24),
district_id int references district(id) on update cascade
);
create table market(
id int not null primary key,
name varchar(24),
area_id int references area(id) on update cascade
);
create table customer(
id int not null primary key,
name varchar(32),
sixweekavg numeric,
market_id int references market(id) on update cascade
);
Right now I have an opportunity to improve that setup as I'm more or less rewriting the site. I looked at this popular page:
What are the options for storing hierarchical data in a relational database?
And I'm sure that my best scenario lies there, but I don't know enough to figure out which one.
It's a reporting site, so there are way more reads than writes. Some of my pages show aggregated data at each level, customer through region (and top, too). So right now on a page that shows district-level data I would write something like:
select d.name, sum(sixweekavg) as avg from customer c
inner join market m on m.id = c.market_id
inner join area a on a.id = m.area_id
inner join district d on d.id = a.district_id
group by d.name order by d.name;
Pretty standard stuff, right? I'm sure a whole separate conversation could be had about materialized views, but for now I'd like to explore a better option for structuring the hierarchy (if that's even the correct term for this).
So given the following summary
PostgreSQL (it can be assumed this will not change)
Fixed hierarchy (my employer may at some point add or remove a tier, but every row in the customer table will always have the same number of "parents")
Significantly more reads than writes
Is there one method that may be better than the others for setting this up?
ltree
I did look at ltree, but I'm not quite sure how that would work. On pages where a user can select a district, for example, I query the district table for the names of each district. I had the idea to add an ltree column in my customers table which would hold the hierarchy, but still maintain the other tables. Is that a feasible and reasonable approach? I've searched for real-world examples of ltree but came up short - most that I found were designed for a random number of parent/child nodes, like a threaded comment section.
I appreciate your help and your patience!

How to Carry over a Table into a Column PostgreSQL

This May be a dumb question as I am a beginner in postgreSQL but what I'm trying to do is
I have a Table called Products and inside products there is 3 columns Name, Price, Expiry Date. Now I have a second table called orders with 4 columns. Product, purchasePrice, Amount, and CountryRecieved.
All I want is to reference the Product column to the product table so it has all the Information of the product table?
Is this do able?
The key concepts you need to read up on are:
"normalisation": the process of breaking down data into multiple related entities
"foreign keys": pointers from one database table to another
"joins": the query construct used to follow that pointer and get the data back together
In your case:
You have correctly determined that the information from Products should not just be copied manually into each row of the Orders table. This is one of the most basic aspects of normalisation: each piece of data is in one place, so updates cannot make it inconsistent.
You have deduced that the Orders table needs some kind of Product column; this is your foreign key. The most common way to represent this is to give the Products table an ID column that uniquely identifies each row, and then have a ProductID column in the Orders table. You could also use the product's name as the key, but this means you can never rename a product, as other entities in the database might reference it; integer keys will generally be more efficient in storage and speed, as well.
To use that foreign key relationship, you use a JOIN in your SQL queries. For example, to get the name and quantity of products ordered, you could write:
SELECT
P.Name,
O.Amount
FROM
Products as P
INNER JOIN
Orders as O
-- This "ON" clause tells the database how to look up the foreign key
On O.ProductId = P.ProductId
ORDER BY
P.Name
Here I've used an "inner join"; there are also "left outer join" and "right outer join", which can be used when only some rows on one side will meet the condition. I recommend you find a tutorial that explains them better than I can in a single paragraph.
Assuming the name column is key in Products table and product column in Orders table refers to it, you can join the two table on related column(s) and get all the information:
select
o.*, p.*
from orders o
join products p on o.product = p.name;

Postgresql: How to relate data between two tables?

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

Database schema dilemma with foreign keys pointing to multiple tables (exclusive arc)

Hopefully my description is a little better than the title, but basically I'm having an issue with one part of a new application schema and i'm stuck on what is the most manageable and elegant solution in table structure.
Bare bones table structure with only relevant fields showing would be as follows:
airline (id, name, ...)
hotel (id, name, ...)
supplier (id, name, ...)
event (id, name,...)
eventComponent (id,name) {e.g Food Catering, Room Hire, Audio/Visual...}
eventFlight (id, eventid, airlineid, ...)
eventHotel (id, eventid, hotelid, ...)
eventSupplier (id, eventid, supplierid, hotelid, eventcomponentid, ...)
So airline, hotel, supplier are all reference tables, and an Event is create with 1 to many relationships between these reference tables. E.g an Event may have 2 flight entries, 3 Other components entries, and 2 hotel entries. But the issue is that in the EventSupplier table the supplier can be either a Supplier or an existing Hotel. So after the user has built their new event on the front-end i need to store this in a fashion that doesn't make it a nightmare to then return this data later.
I've been doing a lot of reading on Polymorphic relations and exclusive arcs and I think my scenario is definitely more along the lines or an Exclusive Arc relationship.
I was thinking:
CREATE TABLE eventSupplier (
id SERIAL PRIMARY KEY,
eventid INT NOT NULL,
hotelid INT,
supplierid INT,
CONSTRAINT UNIQUE (eventid, hotelid, supplierid), -- UNIQUE permits NULLs
CONSTRAINT CHECK (hotelid IS NOT NULL OR supplierid IS NOT NULL),
FOREIGN KEY (hotelid) REFERENCES hotel(id),
FOREIGN KEY (supplierid) REFERENCES supplier(id)
);
And then for the retrieval of this data just use an outer join to both tables to work out which one is linked.
select e.id as eventid, coalesce(h.name,s.name) as supplier
from eventSupplier es
left outer join
supplier s on s.id = es.supplierid
left outer join
hotel h on h.id = es.hotelid
where h.id is not null OR s.id is not null
My other options were to have a single foreign key in the eventSupplier table with another field for the "type" which seems to be a harder solution to retrieve data from, though it does seem quite flexible if I want to extend this down the track without making schema changes. Or alternately to store the hotelid in the Supplier table direct and just declare some suppliers as being a "hotel" though there were then be redundant data which I don't want.
Any thoughts on this would be much appreciated!
Cheers
Phil
How about handling events one-by-one and using an EventGroup to group them together?
EDIT:
I have simply renamed entities to fit the latest comments. This as close as I can get to this -- admittedly I do not understand the problem properly.
A good way to test your solution is to think about what would happen if an airline became a supplier. Does your solution handle that or start to get complicated.
Why do you explicitly need to find hotel data down the supplier route if you don't need that level of data other types of supplier? I would suggest that a supplier is a supplier, whether its a hotel or not for these purposes.
If you want to flag a supplier as a hotel, then simply put hotelid on the supplier table or else wait and hook in the supplier later via whatever mechanism you use to get detail on other suppliers.