postgresql. uniqueness for two columns - postgresql

I need to create unique index for table with two fields (email, alternative_email). It means one email address can be mentioned only one time in two columns. Also alternative email can be empty.
CREATE TABLE customers (
id serial PRIMARY KEY,
email VARCHAR (255) NOT NULL,
alternative_email VARCHAR (255) NOT NULL default ''
);
Data:
insert into customers (email, alternative_email)
values ('test#example.com', ''); - ok
insert into customers (email, alternative_email)
values ('test1#example.com', 'test#example.com');
Second row should not be inserted because alternative_email = 'test#example.com' already mentioned as email in first row.
How to create index to do this ?

You cannot create an index across two columns like that. What you need to do is change your design such that emails are all in one column in a different table.
So you could have a table called emails like:
CREATE TABLE emails (
id serial PRIMARY KEY,
email VARCHAR (255) NOT NULL UNIQUE,
);
Then have a many-to-many table that maps the customers to emails:
CREATE TABLE customer_emails (
customer_id INTEGER NOT NULL REFERENCES customers(id),
email_id INTEGER NOT NULL UNIQUE REFERENCES emails(id),
alternative boolean NOT NULL DEFAULT FALSE,
PRIMARY KEY (customer_id, email_id)
);
CREATE UNIQUE INDEX customer_email_idx ON customer_emails(customer_id, alternative);
This enforces that 2 different customers cannot reuse the same email address, that a customer can have at most one primary and one alternative email address, and the same email address cannot be used for both primary and alternative email addresses of the same customer.

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.

Implementing get or create function

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?

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

PostgreSQL audit table design with Multiple "User types"

I'm trying to implement an Audit table design in PostgreSQL, where I have different types of user id's that can be audited.
Let's say I have a table named admins (which belong to an organization), and table superadmins (which don't).
CREATE TABLE example.organizations (
id SERIAL UNIQUE,
company_name varchar(50) NOT NULL UNIQUE,
phone varchar(20) NOT NULL check (phone ~ '^[0-9]+$')
);
and an example of a potential admin design
CREATE TABLE example.admins (
id serial primary_key,
admin_type varchar not null,
#... shared data
check constraint admin_type in ("super_admins", "regular_admins")
);
CREATE TABLE example.regular_admins (
id integer primary key,
admin_type varchar not null default "regular_admins"
organization_id integer references example.organizations(id),
#... other regular admin fields
foreign key (id, admin_type) references example.admins (id, admin_type),
check constraint admin_type = "regular_admins"
);
CREATE TABLE example.super_admins (
id integer primary key,
admin_type varchar not null default "super_admins"
#... other super admin fields
foreign key (id, admin_type) references example.admins (id, admin_type),
check constraint admin_type = "super_admins"
);
Now an audit table
CREATE TABLE audit.organizations (
audit_timestamp timestamp not null default now(),
operation text,
admin_id integer primary key,
before jsonb,
after jsonb,
);
This calls for inheritance or polymorphism at some level, but I'm curious about how to design it. I've heard that using PostgreSQL's inheritance functionality is not always a great way to go, although I'm finding it to fit this use case.
I'll need to be able to reference a single admin id in the trigger that updates the audit table, and it would be nice to be able to get the admin information when selecting from the audit table without using multiple queries.
Would it be better to use PostgreSQL inheritance or are there other ideas I haven't considered?
I wouldn't say that it calls for inheritance or polymorphism. Admins and superadmins are both types of user, whose only difference is that the former belong to an organization. You can represent this with a single table and a nullable foreign key. No need to overcomplicate matters. Especially if you're using a serial as your primary key type: bad things happen if you confuse admin #2 for superadmin #2.

Unique constraint on 2 fields with special logic in Postgres

I have a table with 3 columns Country,PersonID, Comments.
I want to add a database constraint which will enable multiple records for a PersonID in the same country, but will block adding the same PersonId to multiple countries.
Is it possible?
If you can add another table to the mix, this should be doable:
CREATE TABLE PersonCountries (
PersonID char(7) not null,
Country char(3) not null,
constraint UQ_PersonCountries_Persons UNIQUE (PersonID),
constraint UQ_PersonCountries_XRef UNIQUE (PersonID,Country)
)
You can then have a foreign key from your table to this table on both the PersonID and Country columns. Since PersonID, by itself in the above table has to be unique, you know that each person is only linked to one country. The second unique constraint is to allow the foreign key constraint to be created.