Consider the following design:
CREATE TABLE dbo.Farmers
(
FarmerName varchar(10) NOT NULL
PRIMARY KEY
);
CREATE TABLE dbo.FarmEquipment
(
FarmEquipmentName varchar(10) NOT NULL
PRIMARY KEY
, CreatorFarmer varchar(10) NOT NULL
FOREIGN KEY REFERENCES dbo.Farmers(FarmerName)
ON UPDATE CASCADE
ON DELETE CASCADE
);
CREATE TABLE dbo.Fields
(
FieldName varchar(10) NOT NULL
, OwnerFarmer varchar(10) NOT NULL
FOREIGN KEY REFERENCES dbo.Farmers(FarmerName)
ON UPDATE CASCADE
ON DELETE CASCADE
, FarmEquipment varchar(10) NOT NULL
FOREIGN KEY REFERENCES dbo.FarmEquipment(FarmEquipmentName)
ON UPDATE CASCADE
ON DELETE CASCADE
);
Use case:
- Farmer Joe owns land.
- Farmer Ted owns land.
- Farmer Joe builds a combine-harvester, and loans it to Farmer Ted for use on one of Farmer Ted's fields.
- If Farmer Joe changes his name to Farmer Joseph, I want that to reflect in the
Fields
table, so Farmer Ted knows who owns that combine harvester he's been using.
I realize this is the exact use-case for surrogate keys, but I'm trying to determine how this would work if you're using only natural keys.
In SQL Server, you cannot actually create this structure, since the ON UPDATE CASCADE
and ON UPDATE DELETE
clauses in dbo.Fields
produce the following error message:
Msg 1785, Level 16, State 0, Line 21
Introducing FOREIGN KEY constraint 'FK__Fields__FarmEqui__3AD6B8E2' on table 'Fields' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.Msg 1750, Level 16, State 0, Line 21
Could not create constraint. See previous errors.
How should this model be expressed in SQL Server?
I realize I can just remove the ON UPDATE
clauses, but that's not really the point. I want updates and deletes to cascade.
Best Answer
The solution I've seen has been to remove the foreign key constraint, and to maintain relational integrity via triggers.
On the foreign (or "child") table, you need
INSERT
andUPDATE
triggers. if the "foreign key" column(s) are inserted/updated, then you have to make sure the value(s) exists in the primary key of the "parent" table.On the "parent" table, on
UPDATE
of the primary key column(s), you have to locate all rows in any "child" tables (FarmEquipment
andFields
would both be "children" ofFarmers
) with the old value, and update them to the new value.On the "parent" table, on
DELETE
, you have to check the "child" tables to see if any rows use the primary key(s) in question, and delete them all.Having worked with a vendor-supplied system that worked this way, I can't recommend it.
FarmEquipment
toFields
by mistake. (In the real-world example I mentioned, there was at least one parent table with at least 20 "FK" relationships - sometimes having multiple links in the same child table, to different fields.Documentation of the relationship is no longer built-in. While you can't (to the best of my knowledge) right-click a table and find all foreign key relationships that point to it, it's easy to find a query to retrieve this information from SQL Server's structural tables. When the relationship is maintained via triggers, you pretty much have to go through the triggers manually to identify all the relationships, and the precise definition (the "parent" table's
DELETE
trigger is where you can determine if the relationship is meant to beCASCADE
,NO ACTION
,SET NULL
, orSET DEFAULT
- with foreign key constraints, this can be seen from the "child" table).There are workarounds (the trigger code does exist in a queryable form), but they require that the developers follow strict rules regarding naming conventions, the construction of the query, and possibly even the formatting of the code. Even if one person is maintaining the code, and is using a template to set everything up, keeping everything exactly so as changes are made and bugs are found and fixed becomes much more difficult.
The system I worked with did work, with this method, for more than 10 years; they'd started their app in an environment that didn't have effective foreign keys. But even they had gotten to the point where they were using foreign keys for new tables, and starting to put them in place on existing tables where possible. It's just that working with it wasn't easy.
I suspect this wouldn't meet your criteria; however, as it is a viable solution, I thought it was at least worth covering.