PSQL ON UPDATE interaction with multi-column foreign key - sql

So, I have been tasked with setting up a new DB to handle server patching. I'm still in the design phase and I've been unable to find something to answer this question so I'm reaching out for help.
I have so far:
CREATE TABLE servers (
name varchar(50) PRIMARY KEY,
patch_id varchar(10) REFERENCES patch_info(id) NOT NULL,
env varchar(5),
...
)
CREATE TABLE patch_info (
id varchar(10) PRIMARY KEY,
patch_time timestamptz NOT NULL,
reboot_time timestamptz NOT NULL,
)
CREATE TABLE patch_data (
id big serial PRIMARY KEY,
name varchar(50),
patch_id varchar(10),
env varchar(5),
FOREIGN KEY (name, patch_id, env) REFERENCES servers (name, patch_id, env) NOT NULL ON UPDATE CASCADE ON DELETE CASCADE
)
My question is what is the behavior if I update the patch_id in the servers table. the patch_info table basically just contains a full list of patch/reboot time identifiers referenced by the id value in other tables. The actual patch_info table itself will only change once a month when the ids are updated to reflect time/dates in the current month, but individual servers frequently change their times on a month to month basis, so when the patch_id field changes in the servers table that change needs to cascade down to the patch_data table.
The other question was about the ON DELETE option. I want the patch_data table to cascade a delete if the actual server.name field is deleted, but not if the patch_id is null or removed. Is this the current behavior of my ON CASCADE ON DELETE statement or can I use the patch_data.name column in two separate foreign keys?
Any help is appreciated, thanks.

First, you will need a unique or primary key constraint on servers (name, patch_id, env), since every foreign key must reference either a primary or a unique key.
ON UPDATE CASCADE will update the patch_id column (there are no "fields" in tables) in patch_data if you update patch_id in servers.
ON DELETE CASCADE will delete rows in patch_data if you delete the referenced row from servers.
You cannot "delete a field". Perhaps you mean "update the column to NULL". If the constraint on servers is a primary key constraint, then you cannot do that, because primary key columns are not nullable. If it is a unique constraint, and you update patch_id to NULL in servers, the referencing rows in patch_data will also have patch_id set to NULL. The consequence of that is that the foreign key relationship is no longer effective, because foreign keys are only enforced for columns that are not NULL.
If that sounds confusing, right you are. Consequently, make sure that all involved columns are defined NOT NULL, then nothing weird can happen.

Related

Foreign key constraint cycles or multiple cascade paths

I have a database design like below. I have 3 tables Compartment, CompartmentRelation and CompartmentRelationType . CompartmentRelation table keeps the other compartments around the selected compartment (below,above,behind,infront,etc). CompartmentRelationType keeps the position. Think that i have compartments in the Compartment table named comp-1, comp-2, comp-3, comp-4 and insert the the compartments above comp-1 as comp-2,comp-3 in CompartmentRelation as below. Problem is that setting delete action as cascade for the column RelatedCompId in CompartmentRelation table throw the excaption as
Unable to create relationship 'FK_CompartmentRelation_Compartment1'.
Introducing FOREIGN KEY constraint 'FK_CompartmentRelation_Compartment1' on table 'CompartmentRelation' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.
Which way should i follow ?
Compartment
comp-1
comp-2
comp-3
comp-4
Compartment Relation
comp-1 -> comp-2
comp-1 -> comp-3
CREATE TABLE [dbo].[Compartment] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (500) NOT NULL,
CONSTRAINT [PK_Compartment] PRIMARY KEY CLUSTERED ([Id] ASC),
CREATE TABLE [dbo].[CompartmentRelation] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[CompId] INT NOT NULL,
[RelationTypeId] INT NOT NULL,
[RelatedCompId] INT NOT NULL,
CONSTRAINT [PK_CompartmentRelation] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_CompartmentRelation_CompartmentRelationType] FOREIGN KEY ([RelationTypeId]) REFERENCES [dbo].[CompartmentRelationType] ([Id]) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT [FK_CompartmentRelation_Compartment1] FOREIGN KEY ([RelatedCompId]) REFERENCES [dbo].[Compartment] ([Id]),
CONSTRAINT [FK_CompartmentRelation_Compartment] FOREIGN KEY ([CompId]) REFERENCES [dbo].[Compartment] ([Id]) ON DELETE CASCADE ON UPDATE CASCADE);
CREATE TABLE [dbo].[CompartmentRelationType] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (200) NOT NULL,
[NameLan1] NVARCHAR (200) NOT NULL,
[NameLan2] NVARCHAR (200) NULL,
CONSTRAINT [PK_CompartmentRelationType] PRIMARY KEY CLUSTERED ([Id] ASC)
);
Problem is that setting delete action as cascade for the column
RelatedCompId in CompartmentRelation table throw the excaption as
Unable to create relationship
'FK_CompartmentRelation_Compartment1'. Introducing FOREIGN KEY
constraint 'FK_CompartmentRelation_Compartment1' on table
'CompartmentRelation' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other
FOREIGN KEY constraints. Could not create constraint or index.
See previous errors.
The basic issue appears to be that deletes from table Compartment (and updates to its PKs) can cascade to CompartmentRelation through two different foreign keys. If you intend to support Compartments being related to themselves, then that's end-of-story for cascading from Compartment to CompartmentRelation -- you can't do it.* If you intend to forbid self relationships then you could try adding a check constraint to CompartmentRelation to enforce that prohibition, though I'm not at all sure that SQL Server will take that into account.
If SQL Server won't accept the cascading deletes then you have at least three options:
Make it an application responsibility to clean up compartment relationships before deleting compartments. (And don't cascade.)
Create triggers to handle relationship deletion when compartments are deleted. (And don't cascade.)
Create a stored procedure for deleting compartments, and make it handle the needed relationship deletions. (And don't cascade.)
Which way should i follow ?
Whichever of those makes the most sense for your application. All have advantages and disadvantages.
Additionally,
Do not cascade updates of surrogate key columns, especially when the key values are machine generated, as all yours are. Those keys should never be updated in the first place, and if an attempt were ever made to update one then it would be better for the DB to reject it, for whatever reason, than to accept it.
You probably don't want to cascade deletions of CompartmentRelationType to ComponentRelation. Including such cascading allows for deleting all the relations of a given type by deleting the type itself, but such a cascade is more likely to be performed mistakenly than intentionally, and if it were performed mistakenly then the resulting data loss would be significant. It's probably better to make the application delete all those relations explicitly if that's what it really means to do, and otherwise to reject deletion of types that are in use by existing relations.
*Technically, you could do it by cascading from only one of the two FKs with Compartment, but it seems unlikely that such a half-measure would serve your purposes.

Efficiently enforcing a 1:1 relationship between two rows with foreign key constraints without creating redundant unique indexes

I have two PostgreSQL tables designed in the following way:
create type content_owner as enum (
'document',
'task'
);
create table content (
id serial not null primary key,
owner content_owner not null,
owner_document_id int references document(id) deferrable initially deferred,
owner_task_id int references task(id) deferrable initially deferred,
-- ...
constraint collab_content_owner_document
check (owner_document_id is null or (owner = 'document' and owner_document_id is not null)),
constraint collab_content_owner_task
check (owner_task_id is null or (owner = 'task' and owner_task_id is not null))
);
create table document (
id serial not null primary key,
content_id int not null references content(id),
-- ...
);
create table task (
id serial not null primary key,
content_id int not null references content(id),
-- ...
);
I want to enforce a 1:1 relationship at the database level for the document<->content relationship and the task<->content relationship.
Adding the following constraints accomplishes that:
alter table collab_content add foreign key (owner_document_id, id) references document (id, content_id) deferrable initially deferred;
alter table collab_content add foreign key (owner_task_id, id) references task (id, content_id) deferrable initially deferred;
alter table document add foreign key (content_id, id) references collab_content (id, owner_document_id);
alter table task add foreign key (content_id, id) references collab_content (id, owner_task_id);
Since I’m saying the ID pair should reference the same ID pair in the other table for both directions. However, this also requires me to create the following indexes:
alter table document add unique (id, content_id);
alter table task add unique (id, content_id);
alter table collab_content add unique (id, owner_document_id);
alter table collab_content add unique (id, owner_task_id);
These indexes feel pretty redundant given that there’s already a primary key on the id columns for these tables. It feels like PostgreSQL should be smart enough to be able to use the existing primary key constraint to make sure the foreign key constraints are met. Ideally I wouldn’t create a second, redundant, index on these tables for the purpose of these foreign key constraints.
Is there a way for me to avoid creating new unique indexes and instead tell PostgreSQL to only lookup the unique ID when resolving the foreign key?
Will PostgreSQL detect that these unique indexes are redundant (because the first column is the primary key) and not materialize a new index on disk for their purpose?
Is there a better way to enforce this constraint?
Two-way linking like this is a recipe for headaches. I recommend avoiding reference cycles if you can. In your case, the simplest way to store this information is to relax the constraint that there cannot be a content without a document or a task. Ask yourself, how might such a situation occur, how else could it be avoided, and what damage might it cause if it happens?
If we can remove that constraint, then we can have a very simple structure where document and task each have a content_id foreign key, and a unique index on it to ensure that no two documents have the same content.
If we can't remove that constraint, then the answers to your questions are:
There is no way to avoid creating those new unique indexes for the foreign keys. Foreign keys must have matching unique indexes.
Postgres will not detect that these indexes are redundant, and they will indeed be materialized and take up space.

There are no primary or candidate keys in the referenced table ' ' that match the referencing column list in the foreign key

Okay so I googled and found an endless amount of results obviously, but none of them were helpful.
The error seem to be self explanatory... but I don't see what I'm doing wrong in this case:
CREATE TABLE Contrat
(ID_contrat int identity(1,1),
ID_client int,
code_contrat int,
date_début date,
date_fin date,
status_contrat varchar(20),
Totalité int,
montant_mensuel int,
CONSTRAINT PK_composed2 primary key(ID_contrat,ID_client,code_contrat),
CONSTRAINT FK_3 foreign key(code_contrat) references type_contract(code_type_contract) ON DELETE CASCADE,
CONSTRAINT FK_4 foreign key(ID_Client) references Clients(ID_Client) ON DELETE CASCADE
)
CREATE TABLE FonctionContract
(ID_FonctionContract int identity(1,1),
ID_contract int,
ID_fonction int,
nombre int,
prix_unitaire numeric(16,2),
Constraint PK_composed primary key(ID_fonctionContract, ID_Contract, ID_Fonction),
Constraint FK_11 foreign key(ID_contract) references Contrat(ID_contrat) ON DELETE CASCADE,
Constraint FK_2 foreign key(ID_Fonction) references Fonction(ID_fonction) ON DELETE CASCADE
)
The error is occuring in the before last line (FK_11).
Some results says that I should do a composed foreign key too, but I don't see how when I have ID_FonctionContract not being linked anywhere.
Each value of a Foreign Key constraint needs to reference exactly one row of the target table, and the DBMS needs to be able to guarantee this based on the table definitions.
Although the ID_contrat column is defined as auto-incremented, it is not constrained to be unique, because you've defined a multi-column Primary Key on the Contrat table. So the DBMS is saying that a single ID_contrat in your proposed Foreign Key could potentially match multiple rows in the Contrat table, which is not allowed. (This seems surprising, because you probably know that it will only match one, but the DBMS doesn't!)
You need to do one of two things:
Define your foreign key across multiple columns, so that it matches the multiple columns guaranteed to be unique on the other table; that will require you to have all three columns in the FonctionContrat table (ID_contrat, ID_client, and code_contrat)
Adjust the definition of the Contrat table to say that ID_contrat is in fact unique across all rows, either by changing the Primary Key definition, or adding an additional Unique Constraint on the ID_contrat column
I suspect the second option is the appropriate one here: it's rather unusual to use an auto-increment column and then insert duplicates into it.
What you possibly intended was two different unique constraints / indexes: one on ID_contrat and a separate one on combinations of ID_client and code_contrat. Only one of those can be marked as the Primary Key, but the other can be a Unique Index, which has most of the same functionality in practice; either one can be marked "clustered", which tells SQL Server to physically lay out the table based on that column.

What are alternatives to update/delete records with a key that references multiple tables?

I was reading over some threads in SO, and found out that the example Party table below is incorrect because I have a foreign key referencing two tables which you cannot do. I thought the whole idea of foreign keys was to make sure that if you update or delete in one table, any row that references a foreign key gets updated or deleted. Doesn't the whole idea of limiting a foreign key to reference only a single table kind of kill that idea? What is a way to update/delete all records among all tables when you delete a key that is referenced in other tables?
In my mobile app right now, if a "party" is deleted or renamed, then I basically write a query to check all the appropriate tables and do a DELETE FROM table, WHERE condition wherever I need to. Is this the "normal" way? I thought ON DELETE CASCADE or ON UPDATE CASCADE would do the trick, but if it's only for a single referenced table, then I might as well do it by doing a query on each appropriate table.
I guess another question might be, if I have a foreign key in multiple tables, is that a sign of bad design? Is a foreign key in multiple tables supposed to be a rare occurrence?
CREATE TABLE Party (
partyName VARCHAR(30) NOT NULL,
pcName VARCHAR(30) NOT NULL,
PRIMARY KEY (partyName, pcName),
FOREIGN KEY (partyName, pcName) REFERENCES PC_BASIC (partyName, pcName) ON DELETE CASCADE,
FOREIGN KEY (partyName, pcName) REFERENCES PC_STATS (partyName, pcName) ON DELETE CASCADE
);
CREATE TABLE PC_BASIC (
partyName VARCHAR(30) NOT NULL,
pcName VARCHAR(30) NOT NULL,
init INTEGER NOT NULL,
ac INTEGER NOT NULL
PRIMARY KEY (partyName, pcName)
);
CREATE TABLE PC_STATS (
str integer not null,
dex integer not null,
partyName VARCHAR(30) NOT NULL,
pcName varchar(30) not null,
PRIMARY KEY (partyName, pcName)
);
Yes, in this case it probably indicates there is a problem with your relational schema design.
All three of your tables use the same primary key. I would expect one of the tables to be the "master" or most important table for whatever is represented by the combination of (partyName, pcName). The other two tables may contain supplemental, or perhaps optional information, related to the master table. If so, those two supplemental tables could each have a single foreign key to the master table only.
If that doesn't describe how your data for these tables is actually related in your business case, then please provide more details. For instance, if the data from all three tables is required in all cases, then another option is to combine all three into a single table.
It is not always a design issue to have multiple foreign keys to different tables, but in those cases the foreign keys will involve a different set of columns.

Suggestions for insert with Foreign key constraint in mysql

I have a table containing 2 entries.
Something like
CREATE TABLE `db`.`main` (
`id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
);
The id for these 2 entries are automatically generated primary keys.
I have another table with a rule linking
CREATE TABLE `db`.`day` (
`main_id` int(10) unsigned NOT NULL,
`day` tinyint(4) NOT NULL,
CONSTRAINT `fk_db_main` FOREIGN KEY (`main_id`) REFERENCES `main` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
);
now I can successfully get a result using
SELECT * FROM main where id='9';
but when I try to run
INSERT INTO day (main_id, day) VALUES (9, 0);
I get
"Cannot add or update a child row: a foreign key constraint fails (db.day, CONSTRAINT fk_db_main FOREIGN KEY (main_id) REFERENCES main (id) ON DELETE NO ACTION ON UPDATE NO ACTION) (1452)"
Any suggestions on what I am missing with the insert?
**I hadn't listed the actual cause of the issue while asking the question. The actual cause was that the main db table was in MyISAM, and the InnoDB tables couldn't create a foreign key connecting to it. In short, MyISAM doesn't support foreign keys, even when they are coming from other tables.
The insert works for me if I remove the db. parts in the CREATE TABLE statements (and insert into main a row with an id of 9). Maybe the problem is that you're using that db. prefix inconsistently, i.e. after TABLE but not in the CONSTRAINT clause...?
The FOREIGN KEY constraint says "there shall be an entry in the 'main` table with an ID value that matches the newly inserted 'main_id' value in the 'day' table".
When you INSERT the value 9 into 'day', is there already a row in 'main' with ID = 9?
The DBMS doesn't think so - that's why it complained.
I hadn't listed the actual cause of the issue while asking the question. The actual cause was that the main db table was in MyISAM, and the InnoDB tables couldn't create a foreign key connecting to it. In short, MyISAM doesn't support foreign keys, even when they are coming from other tables.