Being a new user of postgres, I have created a database in postgres 13. It contains tables
including 4 Fields
ID integer (PK)
HoleID varchar(20)
From numeric NOT NULL CHECK (From>=0)
To numeric
Cat varchar (20)
I want to create a constraint that will check that for an identical entity number, the From and To fields of a record must not overlap with another record.
I have proposed the exclude constraint below but it does not work
ADD CONSTRAINT no_overlap EXCLUDE USING GIST ("HoleID" WITH =, ("mFrom", "mTo") WITH &&);
Thank you for helping me.
This is easier with a single numrange column instead of from/to.
create table thing (
id bigserial primary key,
holeid varchar(20),
range numrange not null,
exclude using gist (range with &&, holeid with =)
);
insert into thing (holeid, range) values
('foo', '[1, 10]'::numrange),
-- ok, same holeid, does not overlap
('foo', '[10.1, 11]'::numrange),
-- ok, different holeid, overlaps.
('bar', '[2,5]'::numrange),
-- not ok, same holeid, overlaps.
('foo', '[0, 1]'::numrange);
Demonstration.
Note that [] is inclusive and () is exclusive. (1,10)::numrange and (10,20)::numrange do not overlap. [1,10]::numrange and [10,20]::numrange do overlap.
You can create the constraint like this:
ALTER TABLE tab
ADD CONSTRAINT no_overlap EXCLUDE USING gist (
"HoleID" WITH =,
numrange("mFrom", "mTo") WITH &&
);
You should never store a timestamp as number.
Related
This question already has answers here:
How can you represent inheritance in a database?
(7 answers)
Closed 3 years ago.
I have a table in Postgres which contains Things. Each of these Things can be of 1 of 3 types:
is a self contained Thing
is an instance of a SuperThingA
is an instance of a SuperThingB.
Essentially, in object terms, there's an AbstractThing, extended in
different ways by SuperThingA or SuperThingB and all the records on
the Thing table are either unextended or extended in 1 of the 2
ways.
In order to represent this on the Thing table, I have a number of fields which are common to Things of all types, and I have 2 optional FK reference columns into the SuperThingA and SuperThingB tables.
Ideally, I would like a "FK IF NOT NULL" constraint on each the two, but this doesn't appear to be possible; as far as I can see, all I can do is make both fields nullable and rely on the using code to maintain the FK relationships, which is pretty sucky. This seems to be doable in other databases, for example SQL Server, as per SQL Server 2005: Nullable Foreign Key Constraint, but not any way that I've found so far in PG
How else can I handle this - an insert/update trigger which checks the values when either of those fields is not null and checks the value is present on whichever parent table? That's maybe doable on a small parent table with limited inserts on the Thing table (which, in fairness, is largely the case here - no more than a couple of hundred records in each of the parent tables, and small numbers of inserts on the Thing table), but in a more general case, would be a performance black hole on the inserts if one or both parent table were large
This is not currently enforced with a FK relationship. I've reviewed the PG docs, and it seem pretty definitive that I can't have an optional FK relatioship (which is understandable). It leaves me with a table definition something like this:
CREATE TABLE IF NOT EXISTS Thing(
Thing_id int4 NOT NULL,
Thing_description varchar(40),
Thing_SuperA_FK int4,
Thing_SuperB_FK char(10),
CONSTRAINT ThingPK PRIMARY KEY (Thing_id)
)
;
Every foreign key on a nullable column is only enforced when the value is non-null. This is default behavior.
create table a (
id int4 not null primary key
);
create table b (
id int4 not null primary key,
a_id int4 references a(id)
);
In the example table b has an optional reference to table a.
insert into a values(1); -- entry in table a
insert into b values (1, 1); -- entry in b with reference to a
insert into b values (2, null); -- entry in b with no reference to a
Depending on your use case it also might make sense to reverse your table structure. Instead of having the common table pointing to two more specialized tables you can have it the other way around. This way you avoid the non-null columns entirely.
create table common(
id int4 primary key
-- all the common fields
);
create table special1(
common_id int4 not null references common(id)
-- all the special fields of type special1
);
create table special2(
common_id int4 not null references common(id)
-- all the special fields of type special2
);
You need your SuperN_FK fields defined as nullable foreign keys, then you'll need check constraint(s) on the table to enforce the optional NULLability requirements.
CREATE TABLE Things
( ID int primary key
, col1 varchar(1)
, col2 varchar(1)
, SuperA_FK int constraint fk_SuperA references Things(ID)
, cola1 varchar(1)
, constraint is_SuperA check ((SuperA_FK is null and cola1 is null) or
(SuperA_FK is not null and cola1 is not null))
, SuperB_FK int constraint fk_SuperB references Things(ID)
, colb1 varchar(1)
, constraint is_SuberB check ((SuperB_FK is null and colb1 is null) or
(SuperB_FK is not null))
, constraint Super_Constraint check (
case when SuperA_FK is not null then 1 else 0 end +
case when SuperB_FK is not null then 1 else 0 end
<= 1 )
);
In the above example I've split the check constraints up for ease maintenance. The two is_SuperN constraints enforce the NULL requirements on the FK and it's related detail columns, either all NULLs or the FK is not null and some or all of the detail columns are not null. The final Super_Constraint ensures that at most one SuperN_FK is not null.
I would like to prevent a mismatch between course_code and course_namewhen inserting values to the table below.
CREATE TABLE course (
course_id INT4 NOT NULL PRIMARY KEY,
course_code CHAR(4) NOT NULL,
course_name VARCHAR(30) NOT NULL
);
For both I created an enumeration (see below), now I want to link 'C101' to 'Computer Science' etc.
CREATE TYPE e_course_code AS ENUM (
'C101',
'B102',
'E103',
'V104',
'A105',
'E104'
);
CREATE TYPE e_course_name AS ENUM (
'Computer Science',
'Business Information Management',
'Electronics',
'Visual Programming',
'Audio Technology',
'Engineering'
);
Is it possible to link specified (enumerated) values for two (or even more) columns? Something that returns an error message when inserting a course_code and course_name that do not match?
The fast and reliable tool to implement what you ask in the title would be a foreign key constraint with MATCH FULL:
CREATE TABLE course (
course_code char(4) PRIMARY KEY
, course_name text NOT NULL
);
CREATE TABLE some_other_table (
some_other_id serial PRIMARY KEY
, course_code char(4)
, course_name text
, -- more columns
, CONSTRAINT course_fk FOREIGN KEY (course_code, course_name)
REFERENCES course(course_code, course_name) MATCH FULL
);
Related:
MATCH FULL vs MATCH SIMPLE in foreign key constraints
However, some_other_table.course_name would be completely redundant, and the clean implementation would be the normalized form instead:
CREATE TABLE some_other_table (
some_other_id serial PRIMARY KEY
, course_code char(4)
, -- more columns
, CONSTRAINT course_fk FOREIGN KEY (course_code) REFERENCES course(course_code)
);
Or you add course_id as PK to the course table and use that as FK column.
You can always add a VIEW to display course_name additionally.
The simplest way to solve this (as i see it) would make two separate tables- one with id and code, another with code and name. See this question - Difference between 3NF and BCNF in simple terms (must be able to explain to an 8-year old) - the example in the answer is similar to your problem.
I was wondering whether there was any way possible to reference tableoid's as foreign keys in an inheritance relationship. For example:
CREATE TABLE employee
(
name TEXT,
PRIMARY KEY(name, TABLEOID)
);
CREATE TABLE hourly_employee
(
hours_worked INT,
PRIMARY KEY(name)
) INHERITS(employee);
CREATE TABLE salaried_employee
(
anniversary_date DATE,
PRIMARY KEY(name)
) INHERITS(employee);
CREATE TABLE employee_training
(
training_id SERIAL,
due_date DATE,
employee_name TEXT,
emp_oid OID,
PRIMARY KEY(training_id),
FOREIGN KEY(employee_name, emp_oid) REFERENCES employee(name, TABLEOID)
);
INSERT INTO hourly_employee (name, hours_worked) VALUES ('Joe Smith', 40);
INSERT INTO salaried_employee(name, anniversary_date) VALUES ('Bob Brown', '2014-02-20');
INSERT INTO employee_training (due_date, employee_name, emp_oid) VALUES ('2016-08-16', 'Bob Brown', 'salaried_employee'::REGCLASS);
In this example, the foreign key is created without a problem, but the last insert will fail with the error Key (employee_name, emp_oid)=(Bob Brown, 16403) is not present in table "employee" even though I can confirm that 16403 is the correct tableoid for salaried_employee.
Is there any way to make this work?
Sadly inheritance has some serious limitations. Several elements (including unique indexes / foreign keys) only apply to one table and not the children. Personally I've found it much less useful than I'd have liked it to be.
I know its annoying to suggest you re-design but in my opinion you'd be better to have a single table employee with optional columns instead of the parent / child relations.
CREATE TABLE employee
(
name TEXT,
employee_type TEXT,
hours_worked INT,
anniversary_date DATE,
PRIMARY KEY(name, TABLEOID)
);
In the long run you often find the code becomes simpler and frankly much more portable between DBMS as well.
You can ensure the correct fields have been entered for the correct type using constraints to manage which fields are mandatory for each type.
Eg:
ALTER TABLE employee ADD CHECK (
(type = 'hourly' and hours worked is not null)
or (type = 'salaried' and anniversary_date is not null))
In PostgreSQL 9.5 I'm wanting to create a table with three columns. I'd basically have something like
create table Foo (
account varchar not null,
team_id integer references team (ident) on delete cascade,
league_id integer references league (ident) on delete cascade
)
The fun part now is that I want them to specify EITHER team_id OR league_id, but not both. The combination of account plus one of the other two columns is then the UNIQUE constraint.
Is that possible to do?
To make sure only one of the columns is supplied, use a check constraint:
alter table foo add
constraint check_team check (not (team_id is not null and league_id is not null));
The above will however not prevent providing a NULL value for both columns. If you want to make sure that exactly one of them is provided you can use:
alter table foo add
constraint check_team check ( (team_id is not null or league_id is not null)
and not (team_id is not null and league_id is not null));
Edit: as Abelisto pointed out, the check constraint can be simplified to
alter table foo add
constraint check_team check ((team_id is null) <> (league_id is null));
I'm not sure about the unique constraint you want to establish. If e.g. the following two rows should be prevented ('x', 1, null), ('x', null, 1) then you can use a unique index like this:
create unique index on foo (account, coalesce(team_id, league_id));
That would only work properly if you enforce the rule that at least one of those columns must be not null.
If however you want to allow the same team in different columns, but want to prevent to have he same team_id or league_id twice for an account (allowing the above example) then I think you need to unique indexes:
create unique index on foo (account, team_id) where team_id is not null;
create unique index on foo (account, league_id) where league_id is not null;
This is one strange, unwanted behavior I encountered in Postgres:
When I create a Postgres table with composite primary keys, it enforces NOT NULL constraint on each column of the composite combination.
For example,
CREATE TABLE distributors (m_id integer, x_id integer, PRIMARY KEY(m_id, x_id));
enforces NOT NULL constraint on columns m_id and x_id, which I don't want!
MySQL doesn't do this. I think Oracle doesn't do it as well.
I understand that PRIMARY KEY enforces UNIQUE and NOT NULL automatically but that makes sense for single-column primary key. In a multi-column primary key table, the uniqueness is determined by the combination.
Is there any simple way of avoiding this behavior of Postgres? When I execute this:
CREATE TABLE distributors (m_id integer, x_id integer);
I do not get any NOT NULL constraints of course. But I would not have a primary key either.
If you need to allow NULL values, use a UNIQUE constraint (or index) instead of a PRIMARY KEY (and add a surrogate PK column - I suggest a serial or IDENTITY column in Postgres 10 or later).
Auto increment table column
A UNIQUE constraint allows columns to be NULL:
CREATE TABLE distributor (
distributor_id GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, m_id integer
, x_id integer
, UNIQUE(m_id, x_id) -- !
-- , CONSTRAINT distributor_my_name_uni UNIQUE (m_id, x_id) -- verbose form
);
The manual:
For the purpose of a unique constraint, null values are not considered equal, unless NULLS NOT DISTINCT is specified.
In your case, you could enter something like (1, NULL) for (m_id, x_id) any number of times without violating the constraint. Postgres never considers two NULL values equal - as per definition in the SQL standard.
If you need to treat NULL values as equal (i.e. "not distinct") to disallow such "duplicates", I see two three (since Postgres 15) options:
0. NULLS NOT DISTINCT
This option was added with Postgres 15 and allows to treat NULL values as "not distinct", so two of them conflict in a unique constraint or index. This is the most convenient option, going forward. The manual:
That means even in the presence of a unique constraint it is possible
to store duplicate rows that contain a null value in at least one of
the constrained columns. This behavior can be changed by adding the
clause NULLS NOT DISTINCT ...
Detailed instructions:
Create unique constraint with null columns
1. Two partial indexes
In addition to the UNIQUE constraint above:
CREATE UNIQUE INDEX dist_m_uni_idx ON distributor (m_id) WHERE x_id IS NULL;
CREATE UNIQUE INDEX dist_x_uni_idx ON distributor (x_id) WHERE m_id IS NULL;
But this gets out of hands quickly with more than two columns that can be NULL. See:
Create unique constraint with null columns
2. A multi-column UNIQUE index on expressions
Instead of the UNIQUE constraint. We need a free default value that is never present in involved columns, like -1. Add CHECK constraints to disallow it:
CREATE TABLE distributor (
distributor serial PRIMARY KEY
, m_id integer
, x_id integer
, CHECK (m_id <> -1)
, CHECK (x_id <> -1)
);
CREATE UNIQUE INDEX distributor_uni_idx
ON distributor (COALESCE(m_id, -1), COALESCE(x_id, -1));
When you want a polymorphic relation
Your table uses column names that indicate that they are probably references to other tables:
CREATE TABLE distributors (m_id integer, x_id integer);
So I think you probably are trying to model a polymorphic relation to other tables – where a record in your table distributors can refer to one m record xor one x record.
Polymorphic relations are difficult in SQL. The best resource I have seen about this topic is "Modeling Polymorphic Associations in a Relational Database". There, four alternative options are presented, and the recommendation for most cases is called "Exclusive Belongs To", which in your case would lead to a table like this:
CREATE TABLE distributors (
id serial PRIMARY KEY,
m_id integer REFERENCES m,
x_id integer REFERENCES x,
CHECK (
((m_id IS NOT NULL)::integer + (x_id IS NOT NULL)::integer) = 1
)
);
CREATE UNIQUE INDEX ON distributors (m_id) WHERE m_id IS NOT NULL;
CREATE UNIQUE INDEX ON distributors (x_id) WHERE x_id IS NOT NULL;
Like other solutions, this uses a surrogate primary key column because primary keys are enforced to not contain NULL values in the SQL standard.
This solution adds a 4th option to the three in #Erwin Brandstetter's answer for how to avoid the case where "you could enter something like (1, NULL) for (m_id, x_id) any number of times without violating the constraint." Here, that case is excluded by a combination of two measures:
Partial unique indexes on each column individually: two records (1, NULL) and (1, NULL) would not violate the constraint on the second column as NULLs are considered distinct, but they would violate the constraint on the first column (two records with value 1).
Check constraint: The missing piece is preventing multiple (NULL, NULL) records, still allowed because NULLs are considered distinct, and anyway because our partial indexes do not cover them to save space and write events. This is achieved by the CHECK constraint, which prevents any (NULL, NULL) records by making sure that exactly one column is NULL.
There's one difference though: all alternatives in #Erwin Brandstetter's answer allow at least one record (NULL, NULL) and any number of records with no NULL value in any column (like (1, 2)). When modeling a polymorphic relation, you want to disallow such records. That is achieved by the check constraint in the solution above.