Once you have run into the cycles warning, the only other way to ensure referential integrity without changing your database structure is to use a trigger for one of the constraints. The problem triggering the warning is twofold:
- The
Sections
table shares two opposite-direction foreign key constraints with the Persons
table.
- All the four tables have a full cycle that progresses through all of them in the same direction, Person->Section->Department->Organization.
Additionally, there is an issue with the table design: the way it is constructed, a person could be in charge of a section that he is not a member of.
You can get around two these three problems by removing InCharge
column from Sections
and introducing another table, SectionInCharge
, containing columns PerID
and SecID
, with a clustered index/unique constraint on SecID
, and a composite foreign key to the Persons
table on both columns. You will need to add a UNIQUE constraint to the two columns in the Persons
table so the FK can relate to them.
Now you can set the new FK to ON DELETE CASCADE and delete away.
It is often a superior database design pattern to use the presence or absence of rows to signify something rather than to use a nullable column.
Now that I think about it, the other tables with a "person in charge" column have a similar problem in regards to allowing the assignment of someone who is not properly a member. The only way I know to solve this using FKs (which in my mind is best practice over triggers) is by propagating columns through tables and including them in composite keys. This has the unfortunate side effect of making things a little awkward, but it is not too bad as you can put the most-specific column first and join only on it when possible. So you would put OrgID in the Section
table, with a composite FK to the Dept table on (DeptID, OrgID)
, and then put DeptID
and OrgID
also in the Persons
table. You will then need DeptInCharge
and OrgInCharge
tables.
One more note about this (that's not really about solving the core problem you presented) that you might be interested in is the possibility of combining your three bottom tables into a single table, OrgUnit
. Add an OrgUnitType
column and a ParentOrgID column. Either add columns that will be NULL when the thing does not possess that property (as in Sections not having a fax number), or make the existing Organizations, Departments, and Sections tables subtypes of the OrgUnit (example of DB supertype/subtype). The advantage of this is that then you can refer to any one of the three types anywhere you want to: if the FK is to OrgUnit, it can be any one of them. But if it is only allowed to be a Section, then you would FK to that table instead. In fact, that is then perfect for a single OrgUnitInCharge
table, with an FK to OrgUnit (OrgUnitID, OrgUnitTypeID)
.
I know there are some loose ends here that I haven't tied up completely into a pretty package for you, but I hope that what I've described so far is helpful.
P.S. I hope you will forgive me for adding a stylistic consideration. To me it is a lot of extra needless decoration to put a table-signifying prefix on every column in each table. I type 90-110 wpm depending on how good I feel, but I still hate typing any more than I have to. Also, I believe very strongly in keeping a high signal-to-noise ratio in my code, and for me, the prefixes exploding all over the place drastically reduce that. In SQL queries, best practice is to use table aliases and then use those aliases on each column, as in SELECT P.Name FROM dbo.Person P
. To resolve the issue of multiple tables having the same column names, I have started making the typical WidgetName
column in my Widget
table to also be Widget
. As one example of why a high signal-to-noise is so important: vrbauxCan proYou verRead artThis nouSentence advQuickly conAnd advEasily? True, the additional prefixes contain useful information, but that useful information is only a little useful, and a lot distracting. If you're designing your database from scratch now, I'd like to request, as someone who could by a strange coincidence some day end up maintaining it (stranger things have happened), that you not use those prefixes. :)
P.P.S. I like PascalCase over under_scores because it's easier to type. The underscore is on the top row and hit by the weakest finger--so it's slower and error prone.
P.P.P.S. I also prefer singular table names, but that's neither here nor there is it? I apologize in advance for hitting you up with style critique.