I'm using a PostgreSQL 9.3 Database to create a small Userdatabase. One user can be a part of different groups.
There are 2 Tables in the Database:
user and group
The data type of all columns is text
Every row in user is a single user, every row in group is a single group.One column in user should be groups
How may i configure the groups-column to add multiple entries of group to a single user
What you are describing is a standard many-to-many relationship in a relational database.
You solve this problem by creating a mapping table between users and groups:
If you have something like this currently:
create table users (id integer primary key, user_name text);
create table groups (id integer primary key, group_name text);
The mapping table would look something like this:
create table user_groups
(
user_id not null references users,
group_id not null references groups,
primary key (user_id, group_id)
);
By using (user_id, group_id) as the primary key, you make sure that a user can only be mapped to the same group once.
Related
I handle members' roles in a table with this structure:
id: id of the row
id_member: integer, foreign key is 'id' column in 'members' table
id_role: integer, foreign key is 'id' column in 'roles' table
date_start: timestamp when this user gets the role
date_end: timestamp when this user loses the role
When I add a role, the date_start is set with current_timestamp, and date_end is null.
When I remove a role, the date_end is set with current_timestamp.
I don't want a user to have several roles at the same time, so initially I thought about setting a triple primary key: id_member, id_role and date_end, but it appears I can't put a nullable column as primary key.
How could I change the structure of the table so that I can prevent a user having 2 active roles? I thought about adding a active column but not only would it overcharge the structure, but also I won't be able to save 2 historical roles (if a user was ROLE3 during 4 different periods, for example).
Thanks in advance.
I don't want a user to have several roles at the same time
Partial UNIQUE index
So, each member can only have a single active role (date_end IS NULL).
A partial UNIQUE index will enforce that:
CREATE UNIQUE INDEX tbl_member_active_role_uni ON tbl (id_member)
WHERE date_end IS NULL; -- active role
See:
Create unique constraint with null columns
PostgreSQL multi-column unique constraint and NULL values
EXCLUDE
The above still allows to add historic entries that overlap. To disallow that, too, use an exclusion constraint. You'll need the additional module btree_gist for your integer column. See:
PostgreSQL EXCLUDE USING error: Data type integer has no default operator class
Then:
ALTER TABLE tbl ADD CONSTRAINT tbl_member_no_overlapping_role
EXCLUDE USING gist (id_member with =, tsrange(date_start, date_end) WITH &&);
NULL values for date_end happen to work perfectly. In a range types, NULL as upper bound signifies "unbounded".
See:
How to ensure entries with non-overlapping time ranges?
Currently, I have the following 3 tables.
CREATE TABLE customer (
id SERIAL PRIMARY KEY
);
CREATE TABLE google_subscription (
fk_customer_id INTEGER NOT NULL UNIQUE,
CONSTRAINT fk_customer_id_constraint
FOREIGN KEY(fk_customer_id)
REFERENCES customer(id)
ON DELETE RESTRICT
);
CREATE TABLE apple_subscription (
fk_customer_id INTEGER NOT NULL UNIQUE,
CONSTRAINT fk_customer_id_constraint
FOREIGN KEY(fk_customer_id)
REFERENCES customer(id)
ON DELETE RESTRICT
);
google_subscription is having fk_customer_id referencing to customer table id.
apple_subscription is having fk_customer_id referencing to customer table id.
I was wondering, is it ever possible to create a constraint, such that customer table id, will only be found in either google_subscription or apple_subscription, but NOT both?
No that is not possible. A constraint cannot span across multiple tables. Instead you can have another table subscriptions where you can have a unique index on customer(id). Or you can have another column in customer table which will hold only 1 subscription at a time.
Let's say I have 2 tables: Students and Groups.
The Group table has 2 columns: id, GroupName
The Student table has 3 columns: id, StudentName and GroupID
The GroupID is a foreign key to a Group field.
I need to import the Students table from a CSV, but in my CSV instead of the Group id appears the name of the group. How can I import it with pgAdmin without modifying the csv?
Based on Laurenz answer, use follwoing scripts:
Create a temp table to insert from CSV file:
CREATE TEMP TABLE std_temp (id int, student_name char(25), group_name char(25));
Then, import the CSV file:
COPY std_temp FROM '/home/username/Documents/std.csv' CSV HEADER;
Now, create std and grp tables for students and groups:
CREATE TABLE grp (id int, name char(25));
CREATE TABLE std (id int, name char(20), grp_id int);
It's grp table's turn to be populated based on distinct value of group name. Consider how row_number() is use to provide value for id`:
INSERT INTO grp (id, name) select row_number() OVER (), * from (select distinct group_name from std_temp) as foo;
And the final step, select data based on the join then insert it into the std table:
insert into std (id, name, grp_id) select std_temp.id, std_temp.student_name,grp.id from std_temp inner join grp on std_temp.group_name = grp.name;
At the end, retreive data from final std table:
select * from std;
Your easiest option is to import the file into a temporary table that is defined like the CSV file. Then you can join that table with the "groups" table and use INSERT INTO ... SELECT ... to populate the "students" table.
There is of course also the option to define a view on a join of the two tables and define an INSTEAD OF INSERT trigger on the view that inserts values into the underlying tables as appropriate. Then you could load the data directly to the view.
The suggestion by #LaurenzAlbe is the obvious approach (IMHO never load a spreadsheet directly to
your tables, they are untrustworthy beasts). But I believe your implementation after loading the staging
table is flawed.
First, using row_number() virtually ensures you get duplicated ids for the same group name.
The ids will always increment from 1 by 1 to then number of group names no matter the number of groups previously loaded and you cannot ensure the identical sequence on a subsequent spreadsheets. What happens when you have a group that does not previously exist.
Further there is no validation that the group name does not already exist. Result: Duplicate group names and/or multiple ids for the same name.
Second, you attempt to use the id from the spreadsheet as the id the student (std) table is full of error possibilities. How do you ensure that number is unique across spreadsheets?
Even if unique in a single spreadsheet, how do you ensure another spreadsheet does not use the same numbers as a previous one. Or assuming multiple users create the spreadsheets that one users numbers do not overlap another users even if all users
user are very conscious of the numbers they use. Result: Duplicate id numbers.
A much better approach would be to put a unique key on the group table name column then insert any group names from the stage table into the group trapping any duplicate name errors (using on conflict). Then load the student table directly from the stage table
while selecting group id from the group table by the (now unique) group name.
create table csv_load_temp( junk_num integer, student_name text, group_name text);
create table groups( grp_id integer generated always as identity
, name text
, grp_key text generated always as ( lower(name) ) stored
, constraint grp_pk
primary key (grp_id)
, constraint grp_bk
unique (grp_key)
);
create table students (std_id integer generated always as identity
, name text
, grp_id integer
, constraint std_pk
primary key (std_id)
, constraint std2grp_fk
foreign key (grp_id)
references groups(grp_id)
);
-- Function to load Groups and Students
create or replace function establish_students()
returns void
language sql
as $$
insert into groups (name)
select distinct group_name
from csv_load_temp
on conflict (grp_key) do nothing;
insert into students (name, grp_id)
select student_name, grp_id
from csv_load_temp t
join groups grp
on (grp.name = t.group_name);
$$;
The groups table requires Postgres v12. For prior versions remove the column grp_key couumn
and and put the unique constraint directly on the name column. What to do about capitalization is up to your business logic.
See fiddle for full example. Obviously the 2 inserts in the Establish_Students function can be run standalone and independently. In that case the function itself is not necessary.
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.
So, i'm currently working with a database system where a user can register, login and update his/her details whenever.
The database includes 5 roles:
1. Public
2. Member
3. Moderator
4. Coordinator
5. Admin
I want to be able to assign multiple roles to my users. For example, the Admin can also be a member. Therefore in the database it should show:
User_id | Role_ID
------------------------
user1 | 2, 5
^ is it possible to add multivalued id's in postgresql?
You can use filed of type array to store list of values.
However I think that there is much better way to organize what you want.
Make one table: role_names and another roles, like that:
CREATE TABLE role_names
(
id serial NOT NULL,
name text NOT NULL,
CONSTRAINT role_names_pkey PRIMARY KEY (id),
CONSTRAINT role_names_name_key UNIQUE (name)
);
CREATE TABLE roles
(
user_id bigint NOT NULL,
role_id bigint NOT NULL,
CONSTRAINT roles_pkey PRIMARY KEY (user_id, role_id),
CONSTRAINT roles_role_id_fkey FOREIGN KEY (role_id)
REFERENCES role_names (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE RESTRICT
);
Now in the table role_names you put all the roles that you want to have.
In the table roles, you can assign or delete any number of roles to any user.
Also you can search in table roles for specific users or specific roles - much neat and faster than searching into arrays I think.
Feel free to add FK constraint for the user_id field too.
Yes, you can use int array to store list of roles.
Here's related question -Junction tables vs foreign key arrays?