Implementing get or create function - postgresql

I need to create a function in PostgreSQL using the pl/pgSQL language. I have the following four tables:
CREATE TABLE person (
id serial PRIMARY KEY,
name text NOT NULL,
town_id integer NOT NULL,
country_id integer NOT NULL,
FOREIGN KEY (town_id) references towns (id),
FOREIGN KEY (country_id) references countries (id)
);
CREATE TABLE towns (
id serial PRIMARY KEY,
name text UNIQUE NOT NULL
);
CREATE TABLE countries (
id serial PRIMARY KEY,
name text UNIQUE NOT NULL
);
Now I want to create a function that receives a person record and normalizes this into these tables. The example record is:
name: Jakob
town: Los Angeles
country: USA
In other words, I want to store unique town names in the table towns, and I want to store unique county names in the table countries. I would thus like to implement a "get-or-create" that checks whether a value already exists, and if yes, fetch the primary key. Otherwise, I want to insert the town/country based on the provided name, and fetch the id. Then insert this in the person table.
How would this work as a function in the pl/pgSQL language?

Related

Table with SERIAL field gets indexes automatically in PostgreSQL?

I am working on a database and I need to add indexes to it.
I have a table like this.
CREATE TABLE Customers
(
Id SERIAL PRIMARY KEY,
FirstName CHARACTER VARYING(30),
LastName CHARACTER VARYING(30),
Email CHARACTER VARYING(30),
Age INTEGER
);
CREATE UNIQUE INDEX customers_idx ON Customers (Id);
Do I need to add indexes to it if there is a field with the SERIAL attribute?
As per the documentation,
Adding a primary key will automatically create a unique B-tree index on the column or group of columns listed in the primary key, and will force the column(s) to be marked NOT NULL.
So no, in this case you do not need to create the customers_idx yourself, because you defined that column as a primary key. However, the serial type itself (if the column isn't a primary key or unique) does NOT automatically come with an index.

How do I write this so that the Error: relation "photos" does not exist and same error for "users" does not occur

Im trying to make these two tables however the users and photo table both have a foreign key that refers to the other table and they come up with errors. How would I change this code so that there's no error.
create table people (
id serial,
family_names text,
given_names text,
displayed_names text,
email_address text,
primary key (id)
);
create table users (
userid integer,
website text,
date_registered date,
gender GenderValue,
birthday date,
password text,
portrait integer,
primary key (userid),
foreign key (userid) references people(id),
foreign key (portrait) references photos(photoid)
);
create table photos (
photoid serial,
title TitleValue,
date_uploaded date,
date_taken date,
description text,
technical_details text,
safety_level safetyLevel,
visibility visibilityLevel,
owns integer,
primary key (photoid),
foreign key (owns) references users(userid)
);
ER Diagram
Remove the "forward" reference from the table definition and add it as an alter table statement after all tables have been created:
alter table users add constraint fk_users_photos
foreign key (portrait) references photos(photoid);
In fact, one style of table creation is simply to create all tables with no foreign key references, and then to use alter table for all of them after the table definitions. This prevents any errors and allows circular references.
Here is a db<>fiddle. Note that your code has custom types which I changed to base types in the fiddle.

How to use constraints on ranges with a junction table?

Based on the documentation it's pretty straightforward how to prevent any overlapping reservations in the table at the same time.
CREATE EXTENSION btree_gist;
CREATE TABLE room_reservation (
room text,
during tsrange,
EXCLUDE USING GIST (room WITH =, during WITH &&)
);
However, when you have multiple resources that can be reserved by users, what is the best approach to check for overlappings? You can see below that I want to have users reserve multiple resources. That's why I'm using the junction table Resources_Reservations. Is there any way I can use EXCLUDE in order to check that no resources are reserved at the same time?
CREATE TABLE Users(
id serial primary key,
name text
);
CREATE TABLE Resources(
id serial primary key,
name text
);
CREATE TABLE Reservations(
id serial primary key,
duration tstzrange,
user_id serial,
FOREIGN KEY (user_id) REFERENCES Users(id)
);
CREATE TABLE Resources_Reservations(
resource_id serial,
reservation_id serial,
FOREIGN KEY (resource_id) REFERENCES Resources(id),
FOREIGN KEY (reservation_id) REFERENCES Reservations(id),
PRIMARY KEY (resource_id, reservation_id)
);
I think what you want is doable with a slight model change.
But first let's correct a misconception. You have foreign key columns (user_id, resource_id, etc) defined as SERIAL. This is incorrect, they should be INTEGER. This is because SERIAL is not actually a data type. It is a psuedo-data type that is actually a shortcut for: creating a sequence, creating a column of type integer, and defining the sequence created as the default value. With that out of the way.
I think your Resources_Reservations is redundant. A reservation is by a user, but a reservation without something reserved would just be user information. Bring the resource_id into Reservation. Now a Reservation is by a user for a resource with a duration. Everything your current model contains but less complexity.
Assuming you don't have data that needs saving, then:
create table users(
id serial primary key,
name text
);
create table resources(
id serial primary key,
name text
);
create table reservations(
user_id integer
resource_id integer
duration tstzrange,
foreign key (user_id) references users(id)
foreign key (resource_id) references resources(id),
primary key (resource_id, user_id)
);
You should now be able to create your GIST exclusion.

PSQL enforce uniqueness, but within a subset of a table, not through the entire thing

I'm creating a database of assessments for courses using PostgreSQL.
I'd like assessment names to be unique within the course, but two courses can have assessments with the same name.
-- assessment contains the different assignments & labs that
-- students may submit their code to.
CREATE TABLE assessment (
id SERIAL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL,
comments TEXT NOT NULL,
type ASSESSMENT_TYPE NOT NULL,
course_id SERIAL NOT NULL,
FOREIGN KEY (course_id) REFERENCES courses(id)
);
-- courses contains the information about a course. Since
-- the same course can run multiple times, a single course
-- is uniquely identified by (course_code, year, period)
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL, -- Unique within all courses. Wrong!
course_code VARCHAR(20) NOT NULL,
period PERIOD NOT NULL,
year INTEGER NOT NULL
);
Two main points:
Can I do this without changing the schema?
If so, is there a more idiomatic solution that may include schema changes?
1. Can I do this without changing the schema?
No, since you have multiple issues here.
Your assessments are globally unique by name and not within a course.
assessment.course_id has its own sequence which is useless (SERIAL is just INTEGER + SEQUENCE)
Table courses defines a column data type that does not exist: PERIOD (at least not up to version 11)
2. If so, is there a more idiomatic solution that may include schema changes?
A modified schema that should do what you want would look like this following:
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
course_code VARCHAR(20) NOT NULL,
period tstzrange NOT NULL
);
-- the following is required to build the proper unique constraint...
CREATE EXTENSION IF NOT EXISTS btree_gist;
-- the unique constraint: no two courses with same name at any point in time
ALTER TABLE courses
ADD CONSTRAINT idx_unique_courses
EXCLUDE USING GIST (name WITH =, period WITH &&);
CREATE TABLE assessment (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
comments TEXT NOT NULL,
type ASSESSMENT_TYPE NOT NULL,
course_id INTEGER NOT NULL REFERENCES courses(id),
UNIQUE (course_id, name)
);

Create a foreign key from two different tables

I have two tables in PostgreSQL:
create Table student (
studentID integer primary key,
studentname text
);
create Table courses (
courseID text primary key,
schoolname text
);
I want to create a third table schoolstudent that has a foreign key (studentID, schoolname) where studentID references the primary key of the student table and schoolname references the schoolname key in the courses table.
How can I create a foreign key from two different tables in PostgreSQL 9.4 or 9.5?
A FK constraint requires a UNIQUE or PK constraint on the target column(s), which schoolname obviously cannot provide. You need another table with unique rows per school:
CREATE TABLE school(
school_id serial PRIMARY KEY,
schoolname text NOT NULL
);
CREATE TABLE student(
student_id serial PRIMARY KEY,
studentname text
);
CREATE TABLE schoolstudent(
school_id int REFERENCES school,
student_id int REFERENCES student,
PRIMARY KEY (school_id, student_id)
);
CREATE TABLE course(
course_id text PRIMARY KEY,
school_id int REFERENCES school
);
Using short syntax for foreign key constraints. Details in the manual.
How to implement a many-to-many relationship in PostgreSQL?
If you really need schoolname in the schoolstudent table (I seriously doubt that, looks like a design error), you can just add it. To enforce referential integrity you can include it in the foreign key, but you need a (redundant) matching UNIQUE constraint on school(school_id, schoolname), too.
CREATE TABLE schoolstudent(
school_id int,
student_id int REFERENCES student,
schoolname text,
PRIMARY KEY (school_id, student_id),
CONSTRAINT schoolstudent_combo_fk FOREIGN KEY (school_id, schoolname)
REFERENCES school (school_id, schoolname) ON UPDATE CASCADE
);
Using explicit syntax in this case. And I suggest to cascade updates.
Or if schoolname is actually guaranteed to be UNIQUE (again, my doubts) you can replace school_id completely and just use schoolname as PK and FK column. Long text columns are not very efficient for the purpose, though - if performance matters. And schoolnames change, which is not ideal for PK columns.
You still need a separate school table in any case.
you can set many to many relation only if both the fields are Unique(probably Primary Keys).if above condition is Fulfilled you can use
CREATE TABLE Schoolstudent(
ID INT references student(studentID),
Schoolname CHAR(50) references courses(Schoolname),
);
But schoolname in table courses should be unique or PK.