Postgres - PK with two columns, FK references one of the columns - postgresql

I am adding declarative partitioning to an existing table TEST which already has a PK on ID column.
Another table FOREIGN has a FK on TEST (ID).
I am going to partition TEST by range on TIME_STAMP column, and Postgres is telling me the partitioned column must be part of the PK.
If I update the PK to (ID, TIME_STAMP) that breaks the FK in FOREIGN table.
FOREIGN does not have a TIME_STAMP table so I can't extend the FK to include it.
I have tried adding a unique constaint for ID, but again, postgres tell me the unique constraint must include all columns in the PK.
Any solution to this? Inheritance partitioning is beginning to look much more straight forward.

Related

Avoid scan on attach partition with check constraint

I am recreating an existing table as a partitioned table in PostgreSQL 11.
After some research, I am approaching it using the following procedure so this can be done online while writes are still happening on the table:
add a check constraint on the existing table, first as not valid and then validating
drop the existing primary key
rename the existing table
create the partitioned table under the prior table name
attach the existing table as a partition to the new partitioned table
My expectation was that the last step would be relatively fast, but I don't really have a number for this. In my testing, it's taking about 30s. I wonder if my expectations are incorrect or if I'm doing something wrong with the constraint or anything else.
Here's a simplified version of the DDL.
First, the inserted_at column is declared like this:
inserted_at timestamp without time zone not null
I want to have an index on the ID even after I drop the PK for existing queries and writes, so I create an index:
create unique index concurrently my_events_temp_id_index on my_events (id);
The check constraint is created in one transaction:
alter table my_events add constraint my_events_2022_07_events_check
check (inserted_at >= '2018-01-01' and inserted_at < '2022-08-01')
not valid;
In the next transaction, it's validated (and the validation is successful):
alter table my_events validate constraint my_events_2022_07_events_check;
Then before creating the partitioned table, I drop the primary key of the existing table:
alter table my_events drop constraint my_events_pkey cascade;
Finally, in its own transaction, the partitioned table is created:
alter table my_events rename to my_events_2022_07;
create table my_events (
id uuid not null,
... other columns,
inserted_at timestamp without time zone not null,
primary key (id, inserted_at)
) partition by range (inserted_at);
alter table my_events attach partition my_events_2022_07
for values from ('2018-01-01') to ('2022-08-01');
That last transaction blocks inserts and takes about 30s for the 12M rows in my test database.
Edit
I wanted to add that in response to the attach I see this:
INFO: partition constraint for table "my_events_2022_07" is implied by existing constraints
That makes me think I'm doing this right.
The problem is not the check constraint, it is the primary key.
If you make the original unique index include both columns:
create unique index concurrently my_events_temp_id_index on my_events (id,inserted_at);
And if you make the new table have a unique index rather than a primary key on those two columns, then the attach is nearly instantaneous.
These seem to me like unneeded restrictions in PostgreSQL, both that the unique index on one column can't be used to imply uniqueness on the both columns, and that the unique index on both columns cannot be used to imply the primary key (nor even a unique constraint--but only a unique index).

PostgreSQL declarative partition - unique constraint on partitioned table must include all partitioning columns [duplicate]

This question already has an answer here:
ERROR: unique constraint on partitioned table must include all partitioning columns
(1 answer)
Closed last month.
I'm trying to create a partitioned table which refers to itself, creating a doubly-linked list.
CREATE TABLE test2 (
id serial NOT NULL,
category integer NOT NULL,
time timestamp(6) NOT NULL,
prev_event integer,
next_event integer
) PARTITION BY HASH (category);
Once I add primary key I get the following error.
alter table test2 add primary key (id);
ERROR: unique constraint on partitioned table must include all partitioning columns
DETAIL: PRIMARY KEY constraint on table "test2" lacks column "category" which is part of the partition key.
Why does the unique constrain require all partitioned columns to be included?
EDIT: Now I understand why this is needed: https://www.postgresql.org/docs/current/ddl-partitioning.html#DDL-PARTITIONING-DECLARATIVE-LIMITATIONS
Once I add PK with both columns it works.
alter table test2 add primary key (id, category);
But then adding the FK to itself doesn't work.
alter table test2 add foreign key (prev_event) references test2 (id) on update cascade on delete cascade;
ERROR: there is no unique constraint matching given keys for referenced table "test2"
Since PK is not just id but id-category I can't create FK pointing to id.
Is there any way to deal with this or am I missing something?
I would like to avoid using inheritance partitioning if possible.
EDIT2: It seems this is a known problem. https://www.reddit.com/r/PostgreSQL/comments/di5mbr/postgresql_12_foreign_keys_and_partitioned_tables/f3tsoop/
Seems that there is no straightforward solution. PostgreSQL simply doesn't support this as of v14. One solution is to use triggers to enforce 'foreign key' behavior. Other is to use multi-column foreign keys. Both are far from optimal.

There is no unique constraint matching given keys for referenced

Table: A
columns names
--------------------------
PK varchar |datasource
PK int |programid
int |workspaceid
composite pk key of: datasource and programid
Table: B
columns names
---------------------------------------
PK varchar | datasource
PK int | quantitycontractid
int | workspaceid
composite pk key of: datasource and quantitycontractid
I need to make relationship between those tables but using workspaceid and datasource. So i try as usual:
ALTER TABLE A
ADD CONSTRAINT fk_relation
FOREIGN KEY (workspaceid, datasource)
REFERENCES B(workspaceid, datasource)
I am getting following error:
there is no unique constraint matching given keys for referenced table
"B"
You need to add UNIQUE key to B(workspaceid, datasource) before you consider that as a foreign key in table A. This is to ensure a correct one to one or one to many relationships between the two tables.
ALTER TABLE B
ADD CONSTRAINT unq_contraint
UNIQUE KEY (workspaceid, datasource)
The error makes perfect sense to me, your table B doesn't have any unique index or primary key linked to workspaceid. Having said that, your table structure for B looks kinda weird to me. Most databases have a primary key that is an autoincrement and one or more foreign keys linking to other tables. You seem to have made your primary key a combination of multiple foreign keys. While this works, you're gonna have issues like you described and have complicated joins when your query your tables.
Vishal R already answered on how to fix your problem.

How to edit a record that results in a uniqueness violation and echo the change to child tables?

PostgreSQL 11.1
How can "Editing" of a record be transmitted to dependent records?
Summary of my issue:
The master table, disease, needs a unique constraint on the description column. This unique constraint is needed for foreign key ON UPDATE CASCADE to its children tables.
To allow for a temporary violation of the unique constraint, it must be made deferrable. BUT A DEFERABLE CONSTRAINT CAN NOT BE USED IN A FOREIGN KEY.
Here is the situation.
The database has 100+ tables (and keeps on growing).
Most all information has been normalized in that repeating groups of information have been delegated to their own table.
Following normalization, most tables are lists without duplication of records. Duplication of records within a table is not allowed.
All tables have a unique ID assigned to each record (in addition to a unique constraint placed on the record information).
Most tables are dependent on another table. The foreign keys reference the primary key of the table they are dependent on.
Most unique constraints involve a foreign key (which in turn references the primary key of the parent table).
So, assume the following schema:
CREATE TABLE phoenix.disease
(
recid integer NOT NULL DEFAULT nextval('disease_recid_seq'::regclass),
code text COLLATE pg_catalog."default",
description text COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT disease_pkey PRIMARY KEY (recid),
CONSTRAINT disease_code_unique UNIQUE (code)
DEFERRABLE,
CONSTRAINT disease_description_unique UNIQUE (description)
,
CONSTRAINT disease_description_check CHECK (description <> ''::text)
)
CREATE TABLE phoenix.dx
(
recid integer NOT NULL DEFAULT nextval('dx_recid_seq'::regclass),
disease_recid integer NOT NULL,
patient_recid integer NOT NULL,
CONSTRAINT pk_dx_recid PRIMARY KEY (recid),
CONSTRAINT dx_unique UNIQUE (tposted, patient_recid, disease_recid)
,
CONSTRAINT dx_disease_recid_fkey FOREIGN KEY (disease_recid)
REFERENCES phoenix.disease (recid) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT dx_patients FOREIGN KEY (patient_recid)
REFERENCES phoenix.patients (recid) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE RESTRICT
)
(Columns not involved in this question have been removed. :) )
There are many other children tables of disease with the same basic dependency on the disease table. Note that the primary key of the disease table is a foreign key to the dx table and that the dx table uses this foreign key in a unique constraint. Also note that the dx table is just one table of a long chain of table references. (That is the dx table also has its primary key referenced by other tables).
The problem: I wish to "edit" the contents of the parent disease record. By "edit", I mean:
change the data in the description column.
if the result of the change causes a duplication in the disease table, then one of the "duplicated" records will need to be deleted.
Herein lies my problem. There are many different tables that use the primary key of the disease table in their own unique constraint. If those tables ALSO have a foreign key reference to the duplicated record (in disease), then cascading the delete to those tables would be appropriate -- i.e., no duplication of records will occur.
However, if the child table does NOT have a reference to the "correct" record in the parent disease table, then simply deleting the record (by cascade) will result in loss of information.
Example:
Disease Table:
record 1: ID = 1 description = "ABC"
record 2: ID = 2 description = "DEF"
Dx Table:
record 5: ID = 5 refers to ID=1 of Disease Table.
Editing of record 1 in Disease table results in description becoming "DEF"
Disease Table:
record 1: ID = 1 "ABC" --> "DEF"
I have tried deferring the primary key of the disease table so as to allow the "correct" ID to be "cascaded" to the child tables. This causes the following errors:
A foreign key can not be dependent on a deferred column. "cannot use a deferrable unique constraint for referenced table "disease"
additionally, the parent table (disease) has no way of knowing ahead of time if its children already have a reference to the "correct" record so allowing deletion, or if the child needs to change its own column data to reflect the new "correct" id.
So, how can I allow a change in the parent table (disease) and notify the child tables to change their column values -- and delete within them selves should a duplicate record arise?
Lastly, I do not know today what future tables I will need. So I cannot "precode" into the parent table who its children are or will be.
Thank you for any help with this.

How to ensure that two columns in different tables have the same values

What T-SQL DDL is required to create a constraint that ensures that the values in a column in one table are the same as the values in a column in a different table?
I want to do this without using a PK-FK relationship.
The T-SQL DDL at the end of this post is an example of the generic problem that I'm trying to solve.
In this example, I want to know how to add an equality constraint between the two tables that ensures that the set of values in the column:
"PersonMayDriveCar.personName"
is always equal to the set of values in the column
"DriverLicense.personName"
CREATE SCHEMA "Equality Constraint"
GO
CREATE TABLE "Equality Constraint".PersonMayDriveCar
(
carVin nchar(4000) NOT NULL,
personName nchar(70) NOT NULL,
CONSTRAINT PersonMayDriveCar_PK PRIMARY KEY(personName, carVin)
)
GO
CREATE TABLE "Equality Constraint".DriverLicense
(
driverLicenseNr int NOT NULL,
personName nchar(70) NOT NULL,
CONSTRAINT DriverLicense_PK PRIMARY KEY(driverLicenseNr),
CONSTRAINT DriverLicense_UC UNIQUE(personName)
)
GO
I see that you want to maintain referential integrity between the two tables without using a foreign key.
Based on my past experience, I solved such an issue using a trigger.
So you can create a trigger on the DriverLicense table which ensures that any insert or update into the DriverLicense table will be rolled back if the inserted driverLicenseNr doesn't exist in the PersonMayDriveCar table.
You can go through this for a full example:
https://www.mssqltips.com/sqlservertip/4242/sql-server-referential-integrity-without-foreign-keys/
Adhere to convention:
Use an FK. It’s that simple.
Don’t link these table together with an FK, because they are both child tables of ...
Create a person table, which is the parent of the other two tables
Try this:
Person
- id (PK)
- name
- other columns
PersonMayDriveCar
- person_id (FK to person)
- other columns
DriverLicense
- person_id (FK to person)
- other columns