What is the simplest way to delete a child row when its parent is deleted, without knowing what its parent is? - sql

Given multiple entity types:
Cluster
Hypervisor
VirtualMachine
and given properties that could belong to any one of them (but no more than one per row):
CpuInfo
CpuSpeed
CpuTotal
...
DataStore
...
What is the simplest way to delete a property with its parent?
Attempted Solutions
ON DELETE CASCADE
ON DELETE CASCADE seems to require a nullable foreign key for each possible parent, which strikes me as a poor design:
CREATE TABLE CpuInfo
(
-- Properties
Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
CpuSpeed INT,
AllocatedTotal INT,
CpuTotal INT,
AvailableTotal INT,
-- Foreign keys for all possible parents
ClusterId INT,
HypervisorId INT,
VirtualMachineId INT,
FOREIGN KEY (ClusterId) REFERENCES Cluster(Id) ON DELETE CASCADE,
FOREIGN KEY (HypervisorId) REFERENCES Hypervisor(Id) ON DELETE CASCADE,
FOREIGN KEY (VirtualMachineId) REFERENCES VirtualMachine(Id) ON DELETE CASCADE
);
Junction Tables with Triggers
Parents are related to properties through junction tables. For example:
CREATE TABLE HypervisorCpuInfo
(
HypervisorId INT NOT NULL,
CpuInfoId INT NOT NULL,
FOREIGN KEY (HypervisorId) REFERENCES Hypervisor(Id),
FOREIGN KEY (CpuInfoId) REFERENCES CpuInfo(Id) ON DELETE CASCADE
);
There is then a DELETE trigger for each entity type. The trigger selects the IDs of the entity's properties and deletes them. When the properties are deleted, the child junction rows are then deleted also, via ON CASCADE DELETE.
This doesn't model the business rules very well, though, since it allows the same CpuInfo to belong to multiple entities. It also adds a lot of tables to the design.
Is there a simpler solution?

I think a "junction table" might be fitting for DRYness (it isn't a real junction because of the 1:n relation)
You could call your "junction table" a "super table" (something like "machine" [sorry I'm not native]):
In this table you put all the keys to your properties (make each foreign key column unique to ensure 1:1*). The very type of your "machine" (Cluster,Hypervisor,VirtualMachine) is in the "triple key" you already tried - also in the super-table.
To ensure "machine" is only of one entity add a constraint:
ALTER TABLE CpuInfo WITH CHECK ADD CONSTRAINT [CK_keyIDs] CHECK (
(ClusterId IS NULL AND HypervisorId IS NULL AND VirtualMachineId IS NOT NULL)
OR (ClusterId IS NULL AND HypervisorId IS NOT NULL AND VirtualMachineId IS NULL)
OR (ClusterId IS NOT NULL AND HypervisorId IS NULL AND VirtualMachineId IS NULL)) GO
The good thing is you are quite free with your entities, you could allow a PC to be a Cluster at the same time.
*the key-column! the ID already has to be unique

Related

Postgresql Foreign Key Actions - Delete Attribute and Change Other Attributes Related to This

I create 3 tables just like image. Each students can be enrolled multiple class I tried to build one to many relation.
What I want to do is, when a student is deleted from the "Student" table, the course in which the student is registered in the "Bridge" table returns to null. How can I do this operations with postgresql (pgAdmin 4), can you help me please? Thank you...
You are describing the on delete set null option to foreign keys constraints. The create table statement for bridge would look like:
create table bridge (
std_id int references students(std_id) on delete set null,
class_id int references class(class_id)
);
I am unsure that set null is your best pick for such a bridge table though. This leaves "gaps" in your data that do not make a lot of sense. on delete cascade would probably make more sense - and you could apply it to both foreign keys:
create table bridge (
std_id int references students(std_id) on delete cascade,
class_id int references class(class_id) on delete cascade
);
That way, the bridge table is properly cleaned up when any parent record is dropped. This also opens the way to set up a composite primary key made of both columns in the bridge table.

"Multiple" Foreign Key

I have tables:
MUSICIANS (musician_id, ...)
PROGRAMMERS (programmer_id, ...)
COPS (cop_id, ...)
Then I'm going to have a specific table
RICH_PEOPLE (rich_person_id, ...)
where rich_person_id is either musician_id, programmer_id or cop_id. (Assume that all the musician_ids, programmer_ids, cop_ids are different.)
Is it possible to directly create a Foreign Key on the field rich_person_id?
P.S. I would like the database to
ensure that there is a record of either MUSICIANS, PROGRAMMERS or COPS with the same id as the new RICH_PEOPLE record's rich_person_id before inserting it into RICH_PEOPLE
deleting from either MUSICIANS, PROGRAMMERS or COPS would fail (or require cascade deletion) if there a RICH_PEOPLE record with the same id
P.P.S. I wouldn't like
creating an extra table like POSSIBLY_RICH_PEOPLE with the only field possibly_rich_person_id
creating triggers
You can create three nullable foreign keys, one to each foreign table. Then use a CHECK constraint to ensure only one value is not null at any given time.
For example:
create table rich_people (
rich_person_id int primary key not null,
musician_id int references musicians (musician_id),
programmer_id int references programmers (programmer_id),
cop_id int references cops (cop_id),
check (musician_id is not null and programmer_id is null and cop_id is null
or musician_id is null and programmer_id is not null and cop_id is null
or musician_id is null and programmer_id is null and cop_id is not null)
);
This way, referential integrity will be ensured at all times. Deletions will require cascade deletion or other strategy to keep data integrity.
You do this in a somewhat different way:
Create a table people with a person_id.
Use this key as the primary key (and foreign key) for each of your occupation tables.
Use this key as the primary key (and foreign key) for your rich_people table.
Postgres supports a concept called "inheritance", which facilitates this type construct. Your occupation tables can "inherit" columns from people.

Foreign key to table A or table B

Consider a situation where I define an object, a group of objects, then a table that links them together:
CREATE TABLE obj (
id INTEGER PRIMARY KEY,
name text
) ;
CREATE TABLE group (
id INTEGER PRIMARY KEY ;
grpname TEXT
) ;
CREATE TABLE relation (
objid INTEGER,
grpid INTEGER,
PRIMARY KEY (objid, grpid)
) ;
I am looking for cascade delete when applicable so I add the foreign key
ALTER TABLE relation
ADD FOREIGN KEY (objid)
REFERENCES obj(id)
ON DELETE CASCADE ;
ALTER TABLE relation
ADD FOREIGN KEY (grpid)
REFERENCES group(id)
ON DELETE CASCADE ;
So far is all OK. Now suppose I want to add support for group of groups. I am thinking to change the relation table like this:
CREATE TABLE relation_ver1 (
parent INTEGER,
child INTEGER,
PRIMARY KEY (parent, child)
) ;
ALTER TABLE relation_ver1
ADD FOREIGN KEY (parent)
REFERENCES group(id)
ON DELETE CASCADE ;
Here I get to the question: I would like to apply cascade delete to child too, but I do not know here if child refers to a group or object.
Can I add a foreign key to table obj or group?
The only solution I have found do fare is add child_obj and child_grp fields, add the relative foreign keys and then, when inserting e.g an object use a 'special' (sort of null) group, and do the reverse when inserting subgroup.
Consider the relation:
relation_ver1(parent, child_obj, child_group)
I claim that this relation has the following disadvantages:
You have to deal with the NULL special case.
Approx. 1/3 of values are NULL. NULL values are bad.
Fortunately, there is an easy way to fix this. Since there is a multi-value dependency in your data, you can decompose your table into 2 smaller tables that are 4NF compliant. For example:
relation_ver_obj(parent, child_obj) and
relation_ver_grp(parent, child_group).
The primary reason why we have foreign keys is not so as to be able to do things like cascaded deletes. The primary reason for the existence of foreign keys is referential integrity.
This means that grpid is declared as REFERENCES group(id) in order to ensure that grpid will never be allowed to take any value which is not found in group(id). So, it is an issue of validity. A cascaded DELETE also boils down to validity: if a key is deleted, then any and all foreign keys referring to that key would be left invalid, so clearly, something must be done about them. Cascaded deletion is one possible solution. Setting the foreign key to NULL, thus voiding the relationship, is another possible solution.
Your notion of having a child id refer to either a group or an object violates any notion of referential integrity. Relational Database theory has no use and no provision for polymorphism. A key must refer to one and only one kind of entity. If not, then you start running into problems like the one you have just discovered, but even worse, you cannot have any referential integrity guarantees in your database. That's not a nice situation to be in.
The way to handle the need of relationships to different kinds of entities is with the use of a set of foreign keys, one for each possible related entity, out of which only one may be non-NULL. So, here is how it would look like:
CREATE TABLE tree_relation (
parent_id INTEGER,
child_object_id INTEGER,
child_group_id INTEGER,
PRIMARY KEY (parent_id, child_object_id, child_group_id) );
ALTER TABLE tree_relation
ADD FOREIGN KEY (parent_id) REFERENCES group(id) ON DELETE CASCADE;
ALTER TABLE tree_relation
ADD FOREIGN KEY (child_object_id) REFERENCES object(id) ON DELETE CASCADE;
ALTER TABLE tree_relation
ADD FOREIGN KEY (child_group_id) REFERENCES group(id) ON DELETE CASCADE;
All you need to do is ensure that only one of child_object_id, child_group_id is non-NULL.

How to delete records from parent table which is referenced by multiple child tables?

I have a table which is referenced by multiple tables (around 52) and further,few of the child tables have multiple foreign keys also that is referencing other tables too.
I want to delete a record from parent table, I am unable to do so, as I am getting error "The DELETE statement conflicted with the REFERENCE constraint "FK_xxx". The conflict occurred in database "MyDB", table "dbo.A", column 'x'."
I want a generalized T-SQL solution which is irrespective of tables and number of references.
You have to look at the "on delete" keyword which is a part of the foreign key constraint definition.
Basically you have 4 options:
NO ACTION (does nothing)
CASCADE (deletes the child aswell)
SET NULL (sets the reference field to null)
SET DEFAULT (sets the reference field to the default value)
An example would be:
CREATE TABLE parent (
id INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB;
CREATE TABLE child (
id INT,
parent_id INT,
INDEX par_ind (parent_id),
FOREIGN KEY (parent_id)
REFERENCES parent(id)
ON DELETE CASCADE -- replace CASCADE with your choice
) ENGINE=INNODB;
(for this example and more details look here: http://dev.mysql.com/doc/refman/5.7/en/create-table-foreign-keys.html )
If you now want to modify your constraint, you first have to drop it, and create a new one like for example:
ALTER TABLE child
ADD CONSTRAINT fk_name
FOREIGN KEY (parent_id)
REFERENCES parent(id)
ON DELETE CASCADE; -- replace CASCADE with your choice
I hope this helped. Also to mention it, you should think about maybe not really deleting your parent, and instead creating another boolean column "deleted", which you fill with "yes" if someone clicks the delete. In the "Select"-query you filter then by that "deleted" column.
The advantage is, that you do not lose the history of this entry.
Your problem is this: A FK constraint is designed to prevent you from creating an orphaned child record in any of the 52 tables. I can provide you with the script you seek, but you must realise first that when you try to re-enable the FK constraints the constraints will fail to re-enable because of the orphaned data (which the FK constraints are designed to prevent). For your next step, will have to delete the orphaned data in each of the 52 tables first anyway. It is actually much easier just to redo the constraints with ON DELETE CASCADE, or drop the constraints and forget about referential integrity altogether. You can't have it both ways.

Best approach cascade deleting related entity in MS SQL

Need advice of the best approach how to design DB for the following scenario:
Following below DB structure exmaple (it's not real just explain problem)
File
(
Id INT PRIMARY KEY...,
Name VARCHAR(),
TypeId SMALLINT,
...
/*other common fields*/
)
FileContent
(
Id INT PRIMARY KEY...,
FileId FOREIGN KEY REFERENCES File(Id) NOT NULL ON DELETE CASCADE UNIQUE,
Content VARBINARY(MAX) NOT NULL,
)
Book
(
Id INT PRIMARY KEY...,
Name VARCHAR(255),
Author VARCHAR(255)
...
CoverImageId FK REFERENCES File(Id),
)
BookPageType
(
Id TINYINT PRIMARY KEY...,
Name VARCHAR(50),
)
BookPage
(
Id INT PRIMARY KEY...,
TypyId TINYINT FOREIGN KEY REFERENCES BookPageType(Id),
BookId INT FOREIGN KEY REFERENCES Book(Id) ON DELETE CASCADE,
Name VARCHAR(100),
CreatedDate DATETIME2,
...
/*other common fields*/
)
BookPage1
(
Id PRIMARAY KEY REFERENCES BookPage(Id) NOT NULL ON DELETE CASCADE,
FileId PRIMARAY KEY REFERENCES File(Id)
...
/* other specific fileds */
)
...
BookPageN
(
Id PRIMARAY KEY REFERENCES BookPage(Id) NOT NULL ON DELETE CASCADE,
ImageId PRIMARAY KEY REFERENCES File(Id),
...
/* other specific fileds */
)
Now question is I want to delete Book with all pages and data (and it works good with delete cascade), but how to make cascade delete the associated files also (1 to 1 relentionship).
Here I see following approaches:
Add file to every table when I use it, but I don't want to copy file
schema for every table
Add foreign keys to the File table (instead of page for example), but since I use file for e.g. in 10 tables I will have 10 foreign keys in file table. This also not good
Use triggers, what I don't wnat to do
Thanks in Advance
If such necessary is appeared maybe it seems you need refactor your base.
You said this example is not real and I'll not ask about N tables for pages though it's strange. If not all files have 1 to 1 relationship and so you need remove only a file that other book does not refer to, it's sounds like a job for a trigger.
So what you have defined is a many-to-many relationship between BookPage and File. this is a result of the one-to-many relationship between BookPage and BookPageN and then the one-to-many relationship between File and BookPageN. To get the relationships you say you want in the text, you need to turn the relationship around to point from BookPageN to File. Maybe instead of having so many BookPageN tables you could find a way to consolidate them into a single table. Maybe just use the BookPage table. Just allow nulls for the fields that are optional.